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

use App\Domain\Worker;
use PDO;
use RuntimeException;

final class WorkerRepository
{
    /** Whitelisted updatable columns. Do not extend without also auditing intent. */
    private const UPDATABLE = ['name', 'is_active', 'default_rtb'];

    public function __construct(private readonly PDO $pdo)
    {
    }

    /** @return list<Worker> */
    public function all(): array
    {
        $stmt = $this->pdo->query(
            'SELECT * FROM workers ORDER BY is_active DESC, LOWER(name) ASC'
        );
        $out = [];
        foreach ($stmt as $row) {
            $out[] = self::hydrate($row);
        }
        return $out;
    }

    public function find(int $id): ?Worker
    {
        $stmt = $this->pdo->prepare('SELECT * FROM workers WHERE id = ?');
        $stmt->execute([$id]);
        $row = $stmt->fetch();
        return is_array($row) ? self::hydrate($row) : null;
    }

    public function findByName(string $name): ?Worker
    {
        $stmt = $this->pdo->prepare('SELECT * FROM workers WHERE name = ?');
        $stmt->execute([$name]);
        $row = $stmt->fetch();
        return is_array($row) ? self::hydrate($row) : null;
    }

    /**
     * Active workers that are NOT yet members of the given sprint. Used by
     * the sprint-settings page to populate the "available" list.
     *
     * @return list<Worker>
     */
    public function activeNotInSprint(int $sprintId): array
    {
        $stmt = $this->pdo->prepare(
            'SELECT w.* FROM workers w
             WHERE w.is_active = 1
               AND w.id NOT IN (
                   SELECT sw.worker_id FROM sprint_workers sw WHERE sw.sprint_id = ?
               )
             ORDER BY LOWER(w.name) ASC'
        );
        $stmt->execute([$sprintId]);
        $out = [];
        foreach ($stmt as $row) {
            $out[] = self::hydrate($row);
        }
        return $out;
    }

    /**
     * Insert a new worker. Throws if the unique name constraint is violated.
     */
    public function create(string $name, bool $isActive, float $defaultRtb): Worker
    {
        $now = gmdate('Y-m-d\TH:i:s\Z');
        $stmt = $this->pdo->prepare(
            'INSERT INTO workers (name, is_active, default_rtb, created_at, updated_at)
             VALUES (?, ?, ?, ?, ?)'
        );
        $stmt->execute([$name, $isActive ? 1 : 0, $defaultRtb, $now, $now]);

        $id = (int) $this->pdo->lastInsertId();
        $worker = $this->find($id);
        if ($worker === null) {
            throw new RuntimeException('Inserted worker not found');
        }
        return $worker;
    }

    /**
     * Apply the given changes (only whitelisted columns). Returns before/after
     * snapshots so the caller can drive the AuditLogger.
     *
     * @param array<string,mixed> $changes subset of {name, is_active, default_rtb}
     * @return array{before: Worker, after: Worker}
     */
    public function update(int $id, array $changes): array
    {
        $before = $this->find($id);
        if ($before === null) {
            throw new RuntimeException("Worker {$id} not found");
        }

        $changes = array_intersect_key($changes, array_flip(self::UPDATABLE));
        if ($changes === []) {
            return ['before' => $before, 'after' => $before];
        }

        $sets = [];
        $vals = [];
        foreach ($changes as $col => $v) {
            $sets[] = "{$col} = ?";
            if ($col === 'is_active') {
                $vals[] = (bool) $v ? 1 : 0;
            } elseif ($col === 'default_rtb') {
                $vals[] = (float) $v;
            } else {
                $vals[] = (string) $v;
            }
        }
        $sets[] = 'updated_at = ?';
        $vals[] = gmdate('Y-m-d\TH:i:s\Z');
        $vals[] = $id;

        $stmt = $this->pdo->prepare(
            'UPDATE workers SET ' . implode(', ', $sets) . ' WHERE id = ?'
        );
        $stmt->execute($vals);

        $after = $this->find($id) ?? $before;
        return ['before' => $before, 'after' => $after];
    }

    /**
     * @param array<string,mixed> $row
     */
    private static function hydrate(array $row): Worker
    {
        return new Worker(
            id:         (int) $row['id'],
            name:       (string) $row['name'],
            isActive:   ((int) $row['is_active']) === 1,
            defaultRtb: (float) $row['default_rtb'],
            createdAt:  (string) $row['created_at'],
            updatedAt:  (string) $row['updated_at'],
        );
    }
}
