<?php

declare(strict_types=1);

namespace App\Scolarite\Infrastructure\Persistence\Doctrine;

use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Exception\HomeworkNotFoundException;
use App\Scolarite\Domain\Model\Homework\Homework;
use App\Scolarite\Domain\Model\Homework\HomeworkId;
use App\Scolarite\Domain\Model\Homework\HomeworkStatus;
use App\Scolarite\Domain\Repository\HomeworkRepository;
use App\Shared\Domain\Tenant\TenantId;

use function array_map;

use DateTimeImmutable;
use Doctrine\DBAL\Connection;

use function is_string;
use function json_decode;
use function json_encode;

use const JSON_THROW_ON_ERROR;

use Override;

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

    #[Override]
    public function save(Homework $homework): void
    {
        $this->connection->executeStatement(
            'INSERT INTO homework (id, tenant_id, class_id, subject_id, teacher_id, title, description, due_date, status, rule_override, created_at, updated_at)
             VALUES (:id, :tenant_id, :class_id, :subject_id, :teacher_id, :title, :description, :due_date, :status, :rule_override, :created_at, :updated_at)
             ON CONFLICT (id) DO UPDATE SET
                 title = EXCLUDED.title,
                 description = EXCLUDED.description,
                 due_date = EXCLUDED.due_date,
                 status = EXCLUDED.status,
                 rule_override = EXCLUDED.rule_override,
                 updated_at = EXCLUDED.updated_at',
            [
                'id' => (string) $homework->id,
                'tenant_id' => (string) $homework->tenantId,
                'class_id' => (string) $homework->classId,
                'subject_id' => (string) $homework->subjectId,
                'teacher_id' => (string) $homework->teacherId,
                'title' => $homework->title,
                'description' => $homework->description,
                'due_date' => $homework->dueDate->format('Y-m-d'),
                'status' => $homework->status->value,
                'rule_override' => $homework->ruleOverride !== null ? json_encode($homework->ruleOverride, JSON_THROW_ON_ERROR) : null,
                'created_at' => $homework->createdAt->format(DateTimeImmutable::ATOM),
                'updated_at' => $homework->updatedAt->format(DateTimeImmutable::ATOM),
            ],
        );
    }

    #[Override]
    public function get(HomeworkId $id, TenantId $tenantId): Homework
    {
        $homework = $this->findById($id, $tenantId);

        if ($homework === null) {
            throw HomeworkNotFoundException::withId($id);
        }

        return $homework;
    }

    #[Override]
    public function findById(HomeworkId $id, TenantId $tenantId): ?Homework
    {
        $row = $this->connection->fetchAssociative(
            'SELECT * FROM homework WHERE id = :id AND tenant_id = :tenant_id',
            ['id' => (string) $id, 'tenant_id' => (string) $tenantId],
        );

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

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

    #[Override]
    public function findByTeacher(UserId $teacherId, TenantId $tenantId): array
    {
        $rows = $this->connection->fetchAllAssociative(
            'SELECT * FROM homework
             WHERE teacher_id = :teacher_id
             AND tenant_id = :tenant_id
             AND status != :deleted
             ORDER BY due_date ASC',
            [
                'teacher_id' => (string) $teacherId,
                'tenant_id' => (string) $tenantId,
                'deleted' => HomeworkStatus::DELETED->value,
            ],
        );

        return array_map($this->hydrate(...), $rows);
    }

    #[Override]
    public function findByClass(ClassId $classId, TenantId $tenantId): array
    {
        $rows = $this->connection->fetchAllAssociative(
            'SELECT * FROM homework
             WHERE class_id = :class_id
             AND tenant_id = :tenant_id
             AND status != :deleted
             ORDER BY due_date ASC',
            [
                'class_id' => (string) $classId,
                'tenant_id' => (string) $tenantId,
                'deleted' => HomeworkStatus::DELETED->value,
            ],
        );

        return array_map($this->hydrate(...), $rows);
    }

    #[Override]
    public function delete(HomeworkId $id, TenantId $tenantId): void
    {
        $this->connection->executeStatement(
            'DELETE FROM homework WHERE id = :id AND tenant_id = :tenant_id',
            ['id' => (string) $id, 'tenant_id' => (string) $tenantId],
        );
    }

    /** @param array<string, mixed> $row */
    private function hydrate(array $row): Homework
    {
        /** @var string $id */
        $id = $row['id'];
        /** @var string $tenantId */
        $tenantId = $row['tenant_id'];
        /** @var string $classId */
        $classId = $row['class_id'];
        /** @var string $subjectId */
        $subjectId = $row['subject_id'];
        /** @var string $teacherId */
        $teacherId = $row['teacher_id'];
        /** @var string $title */
        $title = $row['title'];
        /** @var string|null $description */
        $description = $row['description'];
        /** @var string $dueDate */
        $dueDate = $row['due_date'];
        /** @var string $status */
        $status = $row['status'];
        /** @var string $createdAt */
        $createdAt = $row['created_at'];
        /** @var string $updatedAt */
        $updatedAt = $row['updated_at'];
        /** @var string|null $ruleOverrideJson */
        $ruleOverrideJson = $row['rule_override'] ?? null;

        /** @var array{warnings: string[], acknowledgedAt: string}|null $ruleOverride */
        $ruleOverride = is_string($ruleOverrideJson)
            ? json_decode($ruleOverrideJson, true, 512, JSON_THROW_ON_ERROR)
            : null;

        return Homework::reconstitute(
            id: HomeworkId::fromString($id),
            tenantId: TenantId::fromString($tenantId),
            classId: ClassId::fromString($classId),
            subjectId: SubjectId::fromString($subjectId),
            teacherId: UserId::fromString($teacherId),
            title: $title,
            description: $description,
            dueDate: new DateTimeImmutable($dueDate),
            status: HomeworkStatus::from($status),
            createdAt: new DateTimeImmutable($createdAt),
            updatedAt: new DateTimeImmutable($updatedAt),
            ruleOverride: $ruleOverride,
        );
    }
}
