<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Application\Service;

use App\Administration\Application\Service\HomeworkRulesValidator;
use App\Administration\Domain\Model\HomeworkRules\EnforcementMode;
use App\Administration\Domain\Model\HomeworkRules\HomeworkRule;
use App\Administration\Domain\Model\HomeworkRules\HomeworkRules;
use App\Administration\Domain\Model\HomeworkRules\RuleType;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class HomeworkRulesValidatorTest extends TestCase
{
    private HomeworkRulesValidator $validator;

    protected function setUp(): void
    {
        $this->validator = new HomeworkRulesValidator();
    }

    // -- Règles désactivées --

    #[Test]
    public function disabledRulesAlwaysValid(): void
    {
        $rules = $this->creerRulesAvecDelai(3, EnforcementMode::DISABLED, false);

        $result = $this->validator->valider(
            $rules,
            new DateTimeImmutable('2026-03-20'), // vendredi
            new DateTimeImmutable('2026-03-19 15:00'), // jeudi (1 jour, < 3)
        );

        self::assertTrue($result->estValide());
    }

    // -- Délai minimum (AC3) --

    #[Test]
    public function delaiMinimumOkQuandRespected(): void
    {
        $rules = $this->creerRulesAvecDelai(3);

        // Devoir vendredi 20 mars, créé mardi 17 mars = 3 jours = OK
        $result = $this->validator->valider(
            $rules,
            new DateTimeImmutable('2026-03-20'),
            new DateTimeImmutable('2026-03-17 10:00'),
        );

        self::assertTrue($result->estValide());
    }

    #[Test]
    public function delaiMinimumViolationQuandTropTard(): void
    {
        $rules = $this->creerRulesAvecDelai(3);

        // Devoir vendredi 20 mars, créé mercredi 18 mars = 2 jours < 3
        $result = $this->validator->valider(
            $rules,
            new DateTimeImmutable('2026-03-20'),
            new DateTimeImmutable('2026-03-18 10:00'),
        );

        self::assertFalse($result->estValide());
        self::assertCount(1, $result->violations);
        self::assertSame(RuleType::MINIMUM_DELAY, $result->violations[0]->ruleType);
    }

    #[Test]
    public function delaiMinimumSoftModeRetourneAvertissement(): void
    {
        $rules = $this->creerRulesAvecDelai(3, EnforcementMode::SOFT);

        $result = $this->validator->valider(
            $rules,
            new DateTimeImmutable('2026-03-20'),
            new DateTimeImmutable('2026-03-18 10:00'),
        );

        self::assertTrue($result->estAvertissement());
        self::assertFalse($result->estBloquant());
    }

    #[Test]
    public function delaiMinimumHardModeRetourneErreur(): void
    {
        $rules = $this->creerRulesAvecDelai(3, EnforcementMode::HARD);

        $result = $this->validator->valider(
            $rules,
            new DateTimeImmutable('2026-03-20'),
            new DateTimeImmutable('2026-03-18 10:00'),
        );

        self::assertTrue($result->estBloquant());
        self::assertFalse($result->estAvertissement());
    }

    // -- Règle pas de devoir pour lundi (AC4) --

    #[Test]
    public function pasLundiApresOkQuandCreationAvantCutoff(): void
    {
        $rules = $this->creerRulesAvecLundi();

        // Devoir pour lundi 23 mars, créé vendredi 20 mars à 11h (avant 12h)
        $result = $this->validator->valider(
            $rules,
            new DateTimeImmutable('2026-03-23'), // lundi
            new DateTimeImmutable('2026-03-20 11:00'), // vendredi 11h
        );

        self::assertTrue($result->estValide());
    }

    #[Test]
    public function pasLundiApresViolationQuandCreationApresCutoff(): void
    {
        $rules = $this->creerRulesAvecLundi();

        // Devoir pour lundi 23 mars, créé vendredi 20 mars à 14h (après 12h)
        $result = $this->validator->valider(
            $rules,
            new DateTimeImmutable('2026-03-23'), // lundi
            new DateTimeImmutable('2026-03-20 14:00'), // vendredi 14h
        );

        self::assertFalse($result->estValide());
        self::assertSame(RuleType::NO_MONDAY_AFTER, $result->violations[0]->ruleType);
    }

    #[Test]
    public function pasLundiApresIgnorePourJoursNonLundi(): void
    {
        $rules = $this->creerRulesAvecLundi();

        // Devoir pour mardi 24 mars, créé n'importe quand
        $result = $this->validator->valider(
            $rules,
            new DateTimeImmutable('2026-03-24'), // mardi
            new DateTimeImmutable('2026-03-23 20:00'), // lundi soir
        );

        self::assertTrue($result->estValide());
    }

    #[Test]
    public function pasLundiApresViolationSamediCreation(): void
    {
        $rules = $this->creerRulesAvecLundi();

        // Devoir pour lundi 23 mars, créé samedi 21 mars (après vendredi 12h)
        $result = $this->validator->valider(
            $rules,
            new DateTimeImmutable('2026-03-23'), // lundi
            new DateTimeImmutable('2026-03-21 10:00'), // samedi
        );

        self::assertFalse($result->estValide());
    }

    // -- Règles combinées --

    #[Test]
    public function multipleViolationsQuandPlusieursReglesEnfreintes(): void
    {
        $rules = $this->creerRulesComplet();

        // Devoir pour lundi 23 mars, créé samedi 21 mars 10h
        // Violation délai minimum (3 jours: 23-3 = 20, samedi 21 > 20)
        // Violation lundi (après vendredi 12h)
        $result = $this->validator->valider(
            $rules,
            new DateTimeImmutable('2026-03-23'), // lundi
            new DateTimeImmutable('2026-03-21 10:00'), // samedi
        );

        self::assertFalse($result->estValide());
        self::assertCount(2, $result->violations);
    }

    #[Test]
    public function messagesRetourneListeMessages(): void
    {
        $rules = $this->creerRulesAvecDelai(3);

        $result = $this->validator->valider(
            $rules,
            new DateTimeImmutable('2026-03-20'),
            new DateTimeImmutable('2026-03-19 10:00'),
        );

        self::assertCount(1, $result->messages());
        self::assertStringContainsString('3 jours', $result->messages()[0]);
    }

    // -- Helpers --

    private function creerRulesAvecDelai(
        int $days,
        EnforcementMode $mode = EnforcementMode::SOFT,
        bool $enabled = true,
    ): HomeworkRules {
        $rules = HomeworkRules::creer(
            tenantId: TenantId::fromString('550e8400-e29b-41d4-a716-446655440001'),
            now: new DateTimeImmutable('2026-03-01'),
        );

        $rules->mettreAJour(
            rules: [new HomeworkRule(RuleType::MINIMUM_DELAY, ['days' => $days])],
            enforcementMode: $mode,
            enabled: $enabled,
            now: new DateTimeImmutable('2026-03-01'),
        );

        return $rules;
    }

    private function creerRulesAvecLundi(
        EnforcementMode $mode = EnforcementMode::SOFT,
    ): HomeworkRules {
        $rules = HomeworkRules::creer(
            tenantId: TenantId::fromString('550e8400-e29b-41d4-a716-446655440001'),
            now: new DateTimeImmutable('2026-03-01'),
        );

        $rules->mettreAJour(
            rules: [new HomeworkRule(RuleType::NO_MONDAY_AFTER, ['day' => 'friday', 'time' => '12:00'])],
            enforcementMode: $mode,
            enabled: true,
            now: new DateTimeImmutable('2026-03-01'),
        );

        return $rules;
    }

    private function creerRulesComplet(
        EnforcementMode $mode = EnforcementMode::SOFT,
    ): HomeworkRules {
        $rules = HomeworkRules::creer(
            tenantId: TenantId::fromString('550e8400-e29b-41d4-a716-446655440001'),
            now: new DateTimeImmutable('2026-03-01'),
        );

        $rules->mettreAJour(
            rules: [
                new HomeworkRule(RuleType::MINIMUM_DELAY, ['days' => 3]),
                new HomeworkRule(RuleType::NO_MONDAY_AFTER, ['day' => 'friday', 'time' => '12:00']),
            ],
            enforcementMode: $mode,
            enabled: true,
            now: new DateTimeImmutable('2026-03-01'),
        );

        return $rules;
    }
}
