<?php

declare(strict_types=1);

namespace App\Administration\Application\Service;

use App\Administration\Domain\Model\HomeworkRules\HomeworkRule;
use App\Administration\Domain\Model\HomeworkRules\HomeworkRules;
use App\Administration\Domain\Model\HomeworkRules\RuleType;
use DateTimeImmutable;
use LogicException;

use function sprintf;
use function strtolower;

/**
 * Valide une date d'échéance de devoir contre les règles configurées.
 *
 * @see FR81: Configurer règles de timing des devoirs
 */
final readonly class HomeworkRulesValidator
{
    /**
     * Valide si un devoir peut être créé avec la date d'échéance donnée.
     */
    public function valider(
        HomeworkRules $rules,
        DateTimeImmutable $dueDate,
        DateTimeImmutable $creationDate,
    ): HomeworkRulesValidationResult {
        if (!$rules->estActif()) {
            return HomeworkRulesValidationResult::ok($rules->enforcementMode);
        }

        $violations = [];

        foreach ($rules->rules as $rule) {
            $violation = $this->validerRegle($rule, $dueDate, $creationDate);
            if ($violation !== null) {
                $violations[] = $violation;
            }
        }

        return new HomeworkRulesValidationResult(
            violations: $violations,
            enforcementMode: $rules->enforcementMode,
        );
    }

    private function validerRegle(
        HomeworkRule $rule,
        DateTimeImmutable $dueDate,
        DateTimeImmutable $creationDate,
    ): ?RuleViolation {
        return match ($rule->type) {
            RuleType::MINIMUM_DELAY => $this->validerDelaiMinimum($rule, $dueDate, $creationDate),
            RuleType::NO_MONDAY_AFTER => $this->validerPasLundiApres($rule, $dueDate, $creationDate),
        };
    }

    /**
     * AC3 : Configuration "3 jours" → devoir pour vendredi ne peut pas être créé après mardi.
     */
    private function validerDelaiMinimum(
        HomeworkRule $rule,
        DateTimeImmutable $dueDate,
        DateTimeImmutable $creationDate,
    ): ?RuleViolation {
        /** @var int|string $rawDays */
        $rawDays = $rule->params['days'];
        $delayDays = (int) $rawDays;

        $deadline = $dueDate->modify(sprintf('-%d days', $delayDays));
        $creationDay = $creationDate->format('Y-m-d');
        $deadlineDay = $deadline->format('Y-m-d');

        if ($creationDay > $deadlineDay) {
            return new RuleViolation(
                ruleType: RuleType::MINIMUM_DELAY,
                message: sprintf(
                    'Le devoir doit être créé au moins %d jours avant l\'échéance. Date limite de création : %s.',
                    $delayDays,
                    $deadline->format('d/m/Y'),
                ),
            );
        }

        return null;
    }

    /**
     * AC4 : Configuration "après vendredi 12h" → devoir pour lundi ne peut pas être créé après vendredi midi.
     */
    private function validerPasLundiApres(
        HomeworkRule $rule,
        DateTimeImmutable $dueDate,
        DateTimeImmutable $creationDate,
    ): ?RuleViolation {
        $dueDayOfWeek = (int) $dueDate->format('N');

        // Règle ne s'applique que si le devoir est pour lundi (jour 1)
        if ($dueDayOfWeek !== 1) {
            return null;
        }

        /** @var string $rawDay */
        $rawDay = $rule->params['day'];
        $cutoffDay = strtolower($rawDay);
        /** @var string $cutoffTime */
        $cutoffTime = $rule->params['time'];

        $cutoffDayNumber = $this->dayNameToNumber($cutoffDay);

        // Trouver le jour cutoff de la semaine précédente par rapport au lundi
        $daysDiff = $dueDayOfWeek - $cutoffDayNumber;
        if ($daysDiff <= 0) {
            $daysDiff += 7;
        }

        $cutoffDate = $dueDate->modify(sprintf('-%d days', $daysDiff));
        $cutoffDateTime = new DateTimeImmutable(
            $cutoffDate->format('Y-m-d') . ' ' . $cutoffTime,
        );

        if ($creationDate > $cutoffDateTime) {
            return new RuleViolation(
                ruleType: RuleType::NO_MONDAY_AFTER,
                message: sprintf(
                    'Les devoirs pour lundi ne peuvent pas être créés après %s %s.',
                    $this->dayLabel($cutoffDay),
                    $cutoffTime,
                ),
            );
        }

        return null;
    }

    private function dayNameToNumber(string $day): int
    {
        return match ($day) {
            'monday' => 1,
            'tuesday' => 2,
            'wednesday' => 3,
            'thursday' => 4,
            'friday' => 5,
            'saturday' => 6,
            'sunday' => 7,
            default => throw new LogicException(sprintf('Jour invalide "%s" — HomeworkRule aurait dû rejeter cette valeur.', $day)),
        };
    }

    private function dayLabel(string $day): string
    {
        return match ($day) {
            'monday' => 'lundi',
            'tuesday' => 'mardi',
            'wednesday' => 'mercredi',
            'thursday' => 'jeudi',
            'friday' => 'vendredi',
            'saturday' => 'samedi',
            'sunday' => 'dimanche',
            default => throw new LogicException(sprintf('Jour invalide "%s" — HomeworkRule aurait dû rejeter cette valeur.', $day)),
        };
    }
}
