<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Persistence\Doctrine;

use App\Administration\Domain\Exception\CalendrierNonTrouveException;
use App\Administration\Domain\Model\SchoolCalendar\CalendarEntry;
use App\Administration\Domain\Model\SchoolCalendar\CalendarEntryId;
use App\Administration\Domain\Model\SchoolCalendar\CalendarEntryType;
use App\Administration\Domain\Model\SchoolCalendar\SchoolCalendar;
use App\Administration\Domain\Model\SchoolCalendar\SchoolCalendarRepository;
use App\Administration\Domain\Model\SchoolCalendar\SchoolZone;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use Override;

final readonly class DoctrineSchoolCalendarRepository implements SchoolCalendarRepository
{
    public function __construct(
        private Connection $connection,
    ) {
    }

    #[Override]
    public function save(SchoolCalendar $calendar): void
    {
        $this->connection->transactional(function () use ($calendar): void {
            $tenantId = (string) $calendar->tenantId;
            $academicYearId = (string) $calendar->academicYearId;

            $this->connection->executeStatement(
                'DELETE FROM school_calendar_entries WHERE tenant_id = :tenant_id AND academic_year_id = :academic_year_id',
                [
                    'tenant_id' => $tenantId,
                    'academic_year_id' => $academicYearId,
                ],
            );

            foreach ($calendar->entries() as $entry) {
                $this->connection->executeStatement(
                    'INSERT INTO school_calendar_entries (id, tenant_id, academic_year_id, entry_type, start_date, end_date, label, description, zone, created_at)
                     VALUES (:id, :tenant_id, :academic_year_id, :entry_type, :start_date, :end_date, :label, :description, :zone, :created_at)',
                    [
                        'id' => (string) $entry->id,
                        'tenant_id' => $tenantId,
                        'academic_year_id' => $academicYearId,
                        'entry_type' => $entry->type->value,
                        'start_date' => $entry->startDate->format('Y-m-d'),
                        'end_date' => $entry->endDate->format('Y-m-d'),
                        'label' => $entry->label,
                        'description' => $entry->description,
                        'zone' => $calendar->zone?->value,
                        'created_at' => (new DateTimeImmutable())->format(DateTimeImmutable::ATOM),
                    ],
                );
            }
        });
    }

    #[Override]
    public function findByTenantAndYear(TenantId $tenantId, AcademicYearId $academicYearId): ?SchoolCalendar
    {
        $rows = $this->connection->fetchAllAssociative(
            'SELECT * FROM school_calendar_entries
             WHERE tenant_id = :tenant_id
             AND academic_year_id = :academic_year_id
             ORDER BY start_date ASC',
            [
                'tenant_id' => (string) $tenantId,
                'academic_year_id' => (string) $academicYearId,
            ],
        );

        if ($rows === []) {
            return null;
        }

        $entries = array_map(fn (array $row): CalendarEntry => $this->hydrateEntry($row), $rows);

        /** @var string|null $zone */
        $zone = $rows[0]['zone'];

        return SchoolCalendar::reconstitute(
            tenantId: $tenantId,
            academicYearId: $academicYearId,
            zone: $zone !== null ? SchoolZone::from($zone) : null,
            entries: $entries,
        );
    }

    #[Override]
    public function getByTenantAndYear(TenantId $tenantId, AcademicYearId $academicYearId): SchoolCalendar
    {
        $calendar = $this->findByTenantAndYear($tenantId, $academicYearId);

        if ($calendar === null) {
            throw CalendrierNonTrouveException::pourTenantEtAnnee($tenantId, $academicYearId);
        }

        return $calendar;
    }

    /**
     * @param array<string, mixed> $row
     */
    private function hydrateEntry(array $row): CalendarEntry
    {
        /** @var string $id */
        $id = $row['id'];
        /** @var string $entryType */
        $entryType = $row['entry_type'];
        /** @var string $startDate */
        $startDate = $row['start_date'];
        /** @var string $endDate */
        $endDate = $row['end_date'];
        /** @var string $label */
        $label = $row['label'];
        /** @var string|null $description */
        $description = $row['description'];

        return new CalendarEntry(
            id: CalendarEntryId::fromString($id),
            type: CalendarEntryType::from($entryType),
            startDate: new DateTimeImmutable($startDate),
            endDate: new DateTimeImmutable($endDate),
            label: $label,
            description: $description,
        );
    }
}
