<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Persistence\Doctrine;

use App\Administration\Domain\Exception\GradingConfigurationNotFoundException;
use App\Administration\Domain\Model\GradingConfiguration\GradingConfiguration;
use App\Administration\Domain\Model\GradingConfiguration\GradingMode;
use App\Administration\Domain\Model\GradingConfiguration\SchoolGradingConfiguration;
use App\Administration\Domain\Model\GradingConfiguration\SchoolGradingConfigurationId;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Domain\Model\SchoolClass\SchoolId;
use App\Administration\Domain\Repository\GradingConfigurationRepository;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use Override;

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

    #[Override]
    public function save(SchoolGradingConfiguration $configuration): void
    {
        $this->connection->executeStatement(
            'INSERT INTO school_grading_configurations (id, tenant_id, school_id, academic_year_id, grading_mode, configured_at, updated_at)
             VALUES (:id, :tenant_id, :school_id, :academic_year_id, :grading_mode, :configured_at, :updated_at)
             ON CONFLICT (tenant_id, school_id, academic_year_id) DO UPDATE SET
                 grading_mode = EXCLUDED.grading_mode,
                 updated_at = EXCLUDED.updated_at',
            [
                'id' => (string) $configuration->id,
                'tenant_id' => (string) $configuration->tenantId,
                'school_id' => (string) $configuration->schoolId,
                'academic_year_id' => (string) $configuration->academicYearId,
                'grading_mode' => $configuration->gradingConfiguration->mode->value,
                'configured_at' => $configuration->configuredAt->format(DateTimeImmutable::ATOM),
                'updated_at' => $configuration->updatedAt->format(DateTimeImmutable::ATOM),
            ],
        );
    }

    #[Override]
    public function findBySchoolAndYear(
        TenantId $tenantId,
        SchoolId $schoolId,
        AcademicYearId $academicYearId,
    ): ?SchoolGradingConfiguration {
        $row = $this->connection->fetchAssociative(
            'SELECT * FROM school_grading_configurations
             WHERE tenant_id = :tenant_id
             AND school_id = :school_id
             AND academic_year_id = :academic_year_id',
            [
                'tenant_id' => (string) $tenantId,
                'school_id' => (string) $schoolId,
                'academic_year_id' => (string) $academicYearId,
            ],
        );

        if ($row === false) {
            return null;
        }

        return $this->hydrate($row);
    }

    #[Override]
    public function get(SchoolGradingConfigurationId $id, TenantId $tenantId): SchoolGradingConfiguration
    {
        $row = $this->connection->fetchAssociative(
            'SELECT * FROM school_grading_configurations WHERE id = :id AND tenant_id = :tenant_id',
            [
                'id' => (string) $id,
                'tenant_id' => (string) $tenantId,
            ],
        );

        if ($row === false) {
            throw GradingConfigurationNotFoundException::withId($id);
        }

        return $this->hydrate($row);
    }

    /**
     * @param array<string, mixed> $row
     */
    private function hydrate(array $row): SchoolGradingConfiguration
    {
        /** @var string $id */
        $id = $row['id'];
        /** @var string $tenantId */
        $tenantId = $row['tenant_id'];
        /** @var string $schoolId */
        $schoolId = $row['school_id'];
        /** @var string $academicYearId */
        $academicYearId = $row['academic_year_id'];
        /** @var string $gradingMode */
        $gradingMode = $row['grading_mode'];
        /** @var string $configuredAt */
        $configuredAt = $row['configured_at'];
        /** @var string $updatedAt */
        $updatedAt = $row['updated_at'];

        return SchoolGradingConfiguration::reconstitute(
            id: SchoolGradingConfigurationId::fromString($id),
            tenantId: TenantId::fromString($tenantId),
            schoolId: SchoolId::fromString($schoolId),
            academicYearId: AcademicYearId::fromString($academicYearId),
            gradingConfiguration: new GradingConfiguration(GradingMode::from($gradingMode)),
            configuredAt: new DateTimeImmutable($configuredAt),
            updatedAt: new DateTimeImmutable($updatedAt),
        );
    }
}
