<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Persistence\Doctrine;

use App\Administration\Domain\Model\AcademicYear\AcademicPeriod;
use App\Administration\Domain\Model\AcademicYear\PeriodConfiguration;
use App\Administration\Domain\Model\AcademicYear\PeriodType;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Domain\Repository\PeriodConfigurationRepository;
use App\Shared\Domain\Tenant\TenantId;

use function array_map;
use function count;

use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use Override;

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

    #[Override]
    public function save(TenantId $tenantId, AcademicYearId $academicYearId, PeriodConfiguration $configuration): void
    {
        $this->connection->transactional(function () use ($tenantId, $academicYearId, $configuration): void {
            $tenantIdStr = (string) $tenantId;
            $academicYearIdStr = (string) $academicYearId;
            $now = (new DateTimeImmutable())->format(DateTimeImmutable::ATOM);

            $sequences = [];
            foreach ($configuration->periods as $period) {
                $sequences[] = $period->sequence;

                $this->connection->executeStatement(
                    'INSERT INTO academic_periods (tenant_id, academic_year_id, period_type, sequence, label, start_date, end_date, created_at, updated_at)
                     VALUES (:tenant_id, :academic_year_id, :period_type, :sequence, :label, :start_date, :end_date, :created_at, :updated_at)
                     ON CONFLICT (tenant_id, academic_year_id, sequence) DO UPDATE SET
                         period_type = EXCLUDED.period_type,
                         label = EXCLUDED.label,
                         start_date = EXCLUDED.start_date,
                         end_date = EXCLUDED.end_date,
                         updated_at = EXCLUDED.updated_at',
                    [
                        'tenant_id' => $tenantIdStr,
                        'academic_year_id' => $academicYearIdStr,
                        'period_type' => $configuration->type->value,
                        'sequence' => $period->sequence,
                        'label' => $period->label,
                        'start_date' => $period->startDate->format('Y-m-d'),
                        'end_date' => $period->endDate->format('Y-m-d'),
                        'created_at' => $now,
                        'updated_at' => $now,
                    ],
                );
            }

            // Remove any extra periods (e.g. switching from trimester to semester would leave stale rows)
            $this->connection->executeStatement(
                'DELETE FROM academic_periods
                 WHERE tenant_id = :tenant_id
                 AND academic_year_id = :academic_year_id
                 AND sequence > :max_sequence',
                [
                    'tenant_id' => $tenantIdStr,
                    'academic_year_id' => $academicYearIdStr,
                    'max_sequence' => count($configuration->periods),
                ],
            );
        });
    }

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

        if (count($rows) === 0) {
            return null;
        }

        /** @var string $periodType */
        $periodType = $rows[0]['period_type'];

        $periods = array_map(static function (array $row): AcademicPeriod {
            /** @var int|string $sequence */
            $sequence = $row['sequence'];
            /** @var string $label */
            $label = $row['label'];
            /** @var string $startDate */
            $startDate = $row['start_date'];
            /** @var string $endDate */
            $endDate = $row['end_date'];

            return new AcademicPeriod(
                sequence: (int) $sequence,
                label: $label,
                startDate: new DateTimeImmutable($startDate),
                endDate: new DateTimeImmutable($endDate),
            );
        }, $rows);

        return new PeriodConfiguration(PeriodType::from($periodType), $periods);
    }
}
