<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Domain\Model\HomeworkRules;

use App\Administration\Domain\Event\ReglesDevoirsModifiees;
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 HomeworkRulesTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';

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

        self::assertTrue($rules->tenantId->equals(TenantId::fromString(self::TENANT_ID)));
        self::assertSame([], $rules->rules);
        self::assertSame(EnforcementMode::SOFT, $rules->enforcementMode);
        self::assertTrue($rules->enabled);
    }

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

        self::assertCount(0, $rules->pullDomainEvents());
    }

    #[Test]
    public function mettreAJourChangesRulesAndRecordsEvent(): void
    {
        $rules = $this->creerHomeworkRules();
        $now = new DateTimeImmutable('2026-03-17 10:00:00');
        $newRules = [
            new HomeworkRule(RuleType::MINIMUM_DELAY, ['days' => 3]),
        ];

        $rules->mettreAJour(
            rules: $newRules,
            enforcementMode: EnforcementMode::HARD,
            enabled: true,
            now: $now,
        );

        self::assertCount(1, $rules->rules);
        self::assertSame(EnforcementMode::HARD, $rules->enforcementMode);
        self::assertSame($now, $rules->updatedAt);

        $events = $rules->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(ReglesDevoirsModifiees::class, $events[0]);
        self::assertSame([], $events[0]->previousRules);
        self::assertCount(1, $events[0]->newRules);
        self::assertSame(EnforcementMode::HARD, $events[0]->enforcementMode);
    }

    #[Test]
    public function mettreAJourDoesNotRecordEventWhenNothingChanges(): void
    {
        $rules = $this->creerHomeworkRules();
        $now = new DateTimeImmutable('2026-03-17 10:00:00');
        $newRules = [
            new HomeworkRule(RuleType::MINIMUM_DELAY, ['days' => 3]),
        ];

        $rules->mettreAJour(
            rules: $newRules,
            enforcementMode: EnforcementMode::SOFT,
            enabled: true,
            now: $now,
        );
        $rules->pullDomainEvents();

        $rules->mettreAJour(
            rules: $newRules,
            enforcementMode: EnforcementMode::SOFT,
            enabled: true,
            now: new DateTimeImmutable('2026-03-17 11:00:00'),
        );

        self::assertCount(0, $rules->pullDomainEvents());
    }

    #[Test]
    public function mettreAJourWithMultipleRules(): void
    {
        $rules = $this->creerHomeworkRules();
        $now = new DateTimeImmutable('2026-03-17 10:00:00');
        $newRules = [
            new HomeworkRule(RuleType::MINIMUM_DELAY, ['days' => 3]),
            new HomeworkRule(RuleType::NO_MONDAY_AFTER, ['day' => 'friday', 'time' => '12:00']),
        ];

        $rules->mettreAJour(
            rules: $newRules,
            enforcementMode: EnforcementMode::SOFT,
            enabled: true,
            now: $now,
        );

        self::assertCount(2, $rules->rules);
        self::assertSame(RuleType::MINIMUM_DELAY, $rules->rules[0]->type);
        self::assertSame(RuleType::NO_MONDAY_AFTER, $rules->rules[1]->type);
    }

    #[Test]
    public function desactiverDisablesRulesAndRecordsEvent(): void
    {
        $rules = $this->creerHomeworkRules();
        $now = new DateTimeImmutable('2026-03-17 10:00:00');

        $rules->desactiver($now);

        self::assertFalse($rules->enabled);
        self::assertSame(EnforcementMode::DISABLED, $rules->enforcementMode);

        $events = $rules->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(ReglesDevoirsModifiees::class, $events[0]);
        self::assertFalse($events[0]->enabled);
    }

    #[Test]
    public function activerReenablesRulesWithGivenMode(): void
    {
        $rules = $this->creerHomeworkRules();
        $rules->desactiver(new DateTimeImmutable('2026-03-17 09:00:00'));
        $rules->pullDomainEvents();

        $rules->activer(EnforcementMode::HARD, new DateTimeImmutable('2026-03-17 10:00:00'));

        self::assertTrue($rules->enabled);
        self::assertSame(EnforcementMode::HARD, $rules->enforcementMode);

        $events = $rules->pullDomainEvents();
        self::assertCount(1, $events);
    }

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

        self::assertTrue($rules->estActif());
    }

    #[Test]
    public function estActifReturnsFalseWhenDisabled(): void
    {
        $rules = $this->creerHomeworkRules();
        $rules->desactiver(new DateTimeImmutable());

        self::assertFalse($rules->estActif());
    }

    #[Test]
    public function reconstituteFromStorage(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $rulesList = [
            new HomeworkRule(RuleType::MINIMUM_DELAY, ['days' => 3]),
        ];
        $createdAt = new DateTimeImmutable('2026-03-01 10:00:00');
        $updatedAt = new DateTimeImmutable('2026-03-15 14:30:00');
        $id = \App\Administration\Domain\Model\HomeworkRules\HomeworkRulesId::generate();

        $rules = HomeworkRules::reconstitute(
            id: $id,
            tenantId: $tenantId,
            rules: $rulesList,
            enforcementMode: EnforcementMode::HARD,
            enabled: true,
            createdAt: $createdAt,
            updatedAt: $updatedAt,
        );

        self::assertTrue($rules->id->equals($id));
        self::assertTrue($rules->tenantId->equals($tenantId));
        self::assertCount(1, $rules->rules);
        self::assertSame(EnforcementMode::HARD, $rules->enforcementMode);
        self::assertTrue($rules->enabled);
        self::assertSame($createdAt, $rules->createdAt);
        self::assertSame($updatedAt, $rules->updatedAt);
        self::assertCount(0, $rules->pullDomainEvents());
    }

    #[Test]
    public function mettreAJourPreservesPreviousRulesInEvent(): void
    {
        $rules = $this->creerHomeworkRules();
        $now1 = new DateTimeImmutable('2026-03-17 10:00:00');
        $firstRules = [
            new HomeworkRule(RuleType::MINIMUM_DELAY, ['days' => 3]),
        ];

        $rules->mettreAJour(
            rules: $firstRules,
            enforcementMode: EnforcementMode::SOFT,
            enabled: true,
            now: $now1,
        );
        $rules->pullDomainEvents();

        $now2 = new DateTimeImmutable('2026-03-17 11:00:00');
        $secondRules = [
            new HomeworkRule(RuleType::MINIMUM_DELAY, ['days' => 5]),
        ];

        $rules->mettreAJour(
            rules: $secondRules,
            enforcementMode: EnforcementMode::HARD,
            enabled: true,
            now: $now2,
        );

        $events = $rules->pullDomainEvents();
        self::assertCount(1, $events);
        /** @var ReglesDevoirsModifiees $event */
        $event = $events[0];
        self::assertSame('minimum_delay', $event->previousRules[0]['type']);
        self::assertSame(3, $event->previousRules[0]['params']['days']);
        self::assertSame('minimum_delay', $event->newRules[0]['type']);
        self::assertSame(5, $event->newRules[0]['params']['days']);
    }

    private function creerHomeworkRules(): HomeworkRules
    {
        return HomeworkRules::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            now: new DateTimeImmutable('2026-03-01 10:00:00'),
        );
    }
}
