<?php
/*
 * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * See the LICENSE file in the project root for the full license text.
 */

declare(strict_types=1);

namespace App\Tests\Services\Import;

use App\Domain\Import\ParsedSheet;
use App\Domain\TaskAssignment;
use App\Services\Import\XlsxSprintImporter;
use App\Tests\TestCase;

/**
 * Phase 20 — parser smoke test against the real sample workbook
 * (doc/Tool_Sprint Planning.xlsx). Skipped when the host PHP lacks the
 * extensions PhpSpreadsheet needs (ext-dom, ext-zip etc.); the production
 * Docker image and standard CI runners both have them.
 */
final class XlsxSprintImporterTest extends TestCase
{
    private const FIXTURE = __DIR__ . '/../../../doc/Tool_Sprint Planning.xlsx';

    protected function setUp(): void
    {
        foreach (['dom', 'zip', 'xmlreader', 'simplexml', 'gd'] as $ext) {
            if (!extension_loaded($ext)) {
                $this->markTestSkipped("ext-{$ext} not loaded; PhpSpreadsheet cannot run on this host.");
            }
        }
        if (!is_file(self::FIXTURE)) {
            $this->markTestSkipped('Sample workbook not present at ' . self::FIXTURE);
        }
    }

    public function testParsesEverySheet(): void
    {
        $parser = new XlsxSprintImporter();
        $sheets = $parser->parse(self::FIXTURE);

        $this->assertCount(3, $sheets);
        $this->assertSame(['Sprint 1', 'Sprint 2', 'Sprint 3'], array_map(fn(ParsedSheet $s) => $s->sheetName, $sheets));
    }

    public function testSprint1ShapeAndCounts(): void
    {
        $parser = new XlsxSprintImporter();
        $sheets = $parser->parse(self::FIXTURE);
        $s = $sheets[0];

        $this->assertSame(5, count($s->weeks), '5 weeks');
        $this->assertSame(15, count($s->workers), '15 workers');
        $this->assertGreaterThan(20, count($s->tasks), 'more than 20 tasks');

        $this->assertEqualsWithDelta(0.2, $s->reserveFraction, 1e-9, 'reserve fraction = 0.2');

        $kws = array_map(fn($w) => $w->kw, $s->weeks);
        $this->assertSame([13, 14, 15, 16, 17], $kws, 'KWs 13..17 in order');

        $maxDays = array_map(fn($w) => $w->maxWorkingDays, $s->weeks);
        $this->assertSame([2, 4, 4, 5, 2], $maxDays);
    }

    public function testSprint2ColourMappingMatchesSpreadsheet(): void
    {
        $parser = new XlsxSprintImporter();
        $sheets = $parser->parse(self::FIXTURE);
        $s2 = $sheets[1];
        $this->assertSame('Sprint 2', $s2->sheetName);

        $statusCounts = [
            TaskAssignment::STATUS_ZUGEWIESEN    => 0,
            TaskAssignment::STATUS_GESTARTET     => 0,
            TaskAssignment::STATUS_ABGESCHLOSSEN => 0,
            TaskAssignment::STATUS_ABGEBROCHEN   => 0,
        ];
        foreach ($s2->tasks as $t) {
            foreach ($t->assignments as $a) {
                $statusCounts[$a->status]++;
            }
        }

        // From the openpyxl colour audit on the sample:
        //   17× FFFFFF00 + 6× FFFFEB9C + 4× FFFFC000 = 27 yellow/orange  -> gestartet
        //    4× FF00B050 + 1× FFC6EFCE                = 5 green          -> abgeschlossen
        //   the only red-coded cells in the workbook are zero.
        $this->assertSame(27, $statusCounts[TaskAssignment::STATUS_GESTARTET], 'yellow + orange cells = 27');
        $this->assertSame(5,  $statusCounts[TaskAssignment::STATUS_ABGESCHLOSSEN], 'green cells = 5');
        $this->assertSame(0,  $statusCounts[TaskAssignment::STATUS_ABGEBROCHEN], 'no red cells in sample');
    }

    public function testSprint2SkipsArbeitstageGapAndDefinesSixteenWorkers(): void
    {
        // Sprint 2's Arbeitstage block has a blank row at C13 between Titus and
        // Suzan; the parser should resume past the gap and end up with 16 workers.
        $parser = new XlsxSprintImporter();
        $sheets = $parser->parse(self::FIXTURE);
        $s2 = $sheets[1];
        $this->assertCount(16, $s2->workers, 'Sprint 2 has 16 workers (gap row tolerated)');

        $names = array_map(fn($w) => $w->name, $s2->workers);
        $this->assertContains('Suzan', $names);
        $this->assertContains('Nicole', $names);
    }

    public function testRoundTripsViaToArrayFromArray(): void
    {
        $parser = new XlsxSprintImporter();
        $sheets = $parser->parse(self::FIXTURE);
        foreach ($sheets as $orig) {
            $clone = ParsedSheet::fromArray($orig->toArray());
            $this->assertSame($orig->sheetName, $clone->sheetName);
            $this->assertSame(count($orig->weeks),   count($clone->weeks));
            $this->assertSame(count($orig->workers), count($clone->workers));
            $this->assertSame(count($orig->tasks),   count($clone->tasks));
            $this->assertEqualsWithDelta($orig->reserveFraction, $clone->reserveFraction, 1e-9);
        }
    }
}
