<?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\Repositories;

use App\Domain\TaskAssignment;
use App\Repositories\SprintRepository;
use App\Repositories\SprintWorkerRepository;
use App\Repositories\TaskAssignmentRepository;
use App\Repositories\TaskRepository;
use App\Repositories\WorkerRepository;
use App\Tests\TestCase;
use InvalidArgumentException;
use PDO;

/**
 * Phase 18: per-cell status on task_assignments. Verifies upsertStatus
 * semantics, the no-op rule, the "row created with days=0 when status is
 * set on an empty cell" path, and the new statusGridForSprint reader.
 */
final class TaskAssignmentRepositoryTest extends TestCase
{
    /** @return array{PDO,TaskAssignmentRepository,int,int,int,int} pdo, repo, sprintId, swId, taskId, workerId */
    private function seed(): array
    {
        $pdo     = $this->makeDb();
        $sprints = new SprintRepository($pdo);
        $workers = new WorkerRepository($pdo);
        $sw      = new SprintWorkerRepository($pdo);
        $tasks   = new TaskRepository($pdo);
        $repo    = new TaskAssignmentRepository($pdo);

        $sprint = $sprints->create('S', '2026-01-05', '2026-01-30', 0.2);
        $sprints->materializeWeeks($sprint->id, '2026-01-05', '2026-01-30', 4);
        $worker = $workers->create('Alice', true, 0.0);
        $sworker = $sw->add($sprint->id, $worker->id, 0.0);
        $task    = $tasks->create($sprint->id, 'Build thing', null, 1);

        return [$pdo, $repo, $sprint->id, $sworker->id, $task->id, $worker->id];
    }

    public function testUpsertStatusOnEmptyCellWithDefaultIsNoop(): void
    {
        [, $repo, , $swId, $taskId] = $this->seed();

        $r = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_ZUGEWIESEN);

        $this->assertSame('NOOP', $r['action']);
        $this->assertNull($r['before']);
        $this->assertNull($r['after']);
        $this->assertNull($repo->find($taskId, $swId));
    }

    public function testUpsertStatusOnEmptyCellWithExplicitCreatesZeroDayRow(): void
    {
        [, $repo, , $swId, $taskId] = $this->seed();

        $r = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_GESTARTET);

        $this->assertSame('CREATE', $r['action']);
        $this->assertNull($r['before']);
        $this->assertNotNull($r['after']);
        $this->assertSame(0.0, $r['after']->days);
        $this->assertSame(TaskAssignment::STATUS_GESTARTET, $r['after']->status);

        // Reload to confirm persistence.
        $loaded = $repo->find($taskId, $swId);
        $this->assertNotNull($loaded);
        $this->assertSame(TaskAssignment::STATUS_GESTARTET, $loaded->status);
    }

    public function testUpsertStatusUnchangedIsNoop(): void
    {
        [, $repo, , $swId, $taskId] = $this->seed();

        $repo->upsert($taskId, $swId, 1.5);
        $first = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_GESTARTET);
        $this->assertSame('UPDATE', $first['action']);

        $again = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_GESTARTET);
        $this->assertSame('NOOP', $again['action']);
        $this->assertSame(TaskAssignment::STATUS_GESTARTET, $again['after']->status);
    }

    public function testUpsertStatusUpdatesExistingRowAndKeepsDays(): void
    {
        [, $repo, , $swId, $taskId] = $this->seed();

        $repo->upsert($taskId, $swId, 2.5);
        $r = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_ABGESCHLOSSEN);

        $this->assertSame('UPDATE', $r['action']);
        $this->assertSame(2.5,                                $r['before']->days);
        $this->assertSame(TaskAssignment::STATUS_ZUGEWIESEN,  $r['before']->status);
        $this->assertSame(2.5,                                $r['after']->days);
        $this->assertSame(TaskAssignment::STATUS_ABGESCHLOSSEN, $r['after']->status);
    }

    public function testUpsertDaysOnExistingPreservesStatus(): void
    {
        [, $repo, , $swId, $taskId] = $this->seed();

        $repo->upsert($taskId, $swId, 1.0);
        $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_GESTARTET);
        $r = $repo->upsert($taskId, $swId, 3.0);

        $this->assertSame('UPDATE', $r['action']);
        $this->assertSame(TaskAssignment::STATUS_GESTARTET, $r['after']->status);
    }

    public function testUpsertStatusRejectsUnknownValue(): void
    {
        [, $repo, , $swId, $taskId] = $this->seed();

        $this->expectException(InvalidArgumentException::class);
        $repo->upsertStatus($taskId, $swId, 'in-flight');
    }

    public function testStatusGridForSprintGroupsByTaskAndWorker(): void
    {
        [, $repo, $sprintId, $swId, $taskId] = $this->seed();

        $repo->upsert($taskId, $swId, 1.0);
        $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_ABGEBROCHEN);

        $grid = $repo->statusGridForSprint($sprintId);
        $this->assertArrayHasKey($taskId, $grid);
        $this->assertArrayHasKey($swId, $grid[$taskId]);
        $this->assertSame(TaskAssignment::STATUS_ABGEBROCHEN, $grid[$taskId][$swId]);
    }

    public function testHydrateOnFreshUpsertCarriesDefaultStatus(): void
    {
        [, $repo, , $swId, $taskId] = $this->seed();

        $r = $repo->upsert($taskId, $swId, 1.0);
        $this->assertSame('CREATE', $r['action']);
        $this->assertSame(TaskAssignment::STATUS_ZUGEWIESEN, $r['after']->status);

        $loaded = $repo->find($taskId, $swId);
        $this->assertNotNull($loaded);
        $this->assertSame(TaskAssignment::STATUS_ZUGEWIESEN, $loaded->status);
    }
}
