<?php

declare(strict_types=1);

namespace App\Scolarite\Infrastructure\Persistence\Doctrine;

use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Exception\RenduNonTrouveException;
use App\Scolarite\Domain\Model\Homework\HomeworkId;
use App\Scolarite\Domain\Model\HomeworkSubmission\HomeworkSubmission;
use App\Scolarite\Domain\Model\HomeworkSubmission\HomeworkSubmissionId;
use App\Scolarite\Domain\Model\HomeworkSubmission\SubmissionStatus;
use App\Scolarite\Domain\Repository\HomeworkSubmissionRepository;
use App\Shared\Domain\Tenant\TenantId;

use function array_fill_keys;
use function array_map;

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

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

    #[Override]
    public function save(HomeworkSubmission $submission): void
    {
        $this->connection->executeStatement(
            'INSERT INTO homework_submissions (id, tenant_id, homework_id, student_id, response_html, status, submitted_at, created_at, updated_at)
             VALUES (:id, :tenant_id, :homework_id, :student_id, :response_html, :status, :submitted_at, :created_at, :updated_at)
             ON CONFLICT (id) DO UPDATE SET
                 response_html = EXCLUDED.response_html,
                 status = EXCLUDED.status,
                 submitted_at = EXCLUDED.submitted_at,
                 updated_at = EXCLUDED.updated_at',
            [
                'id' => (string) $submission->id,
                'tenant_id' => (string) $submission->tenantId,
                'homework_id' => (string) $submission->homeworkId,
                'student_id' => (string) $submission->studentId,
                'response_html' => $submission->responseHtml,
                'status' => $submission->status->value,
                'submitted_at' => $submission->submittedAt?->format(DateTimeImmutable::ATOM),
                'created_at' => $submission->createdAt->format(DateTimeImmutable::ATOM),
                'updated_at' => $submission->updatedAt->format(DateTimeImmutable::ATOM),
            ],
        );
    }

    #[Override]
    public function get(HomeworkSubmissionId $id, TenantId $tenantId): HomeworkSubmission
    {
        $submission = $this->findById($id, $tenantId);

        if ($submission === null) {
            throw RenduNonTrouveException::withId($id);
        }

        return $submission;
    }

    #[Override]
    public function findById(HomeworkSubmissionId $id, TenantId $tenantId): ?HomeworkSubmission
    {
        $row = $this->connection->fetchAssociative(
            'SELECT * FROM homework_submissions 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 findByHomeworkAndStudent(
        HomeworkId $homeworkId,
        UserId $studentId,
        TenantId $tenantId,
    ): ?HomeworkSubmission {
        $row = $this->connection->fetchAssociative(
            'SELECT * FROM homework_submissions
             WHERE homework_id = :homework_id
             AND student_id = :student_id
             AND tenant_id = :tenant_id',
            [
                'homework_id' => (string) $homeworkId,
                'student_id' => (string) $studentId,
                'tenant_id' => (string) $tenantId,
            ],
        );

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

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

    #[Override]
    public function findByHomework(HomeworkId $homeworkId, TenantId $tenantId): array
    {
        $rows = $this->connection->fetchAllAssociative(
            'SELECT * FROM homework_submissions
             WHERE homework_id = :homework_id
             AND tenant_id = :tenant_id
             ORDER BY submitted_at ASC NULLS LAST, created_at ASC',
            [
                'homework_id' => (string) $homeworkId,
                'tenant_id' => (string) $tenantId,
            ],
        );

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

    #[Override]
    public function findStatusesByStudent(UserId $studentId, TenantId $tenantId, HomeworkId ...$homeworkIds): array
    {
        if ($homeworkIds === []) {
            return [];
        }

        $ids = array_map(static fn (HomeworkId $id): string => (string) $id, $homeworkIds);

        /** @var array<array{homework_id: string, status: string}> $rows */
        $rows = $this->connection->fetchAllAssociative(
            'SELECT homework_id, status FROM homework_submissions
             WHERE student_id = :student_id
             AND tenant_id = :tenant_id
             AND homework_id IN (:homework_ids)',
            [
                'student_id' => (string) $studentId,
                'tenant_id' => (string) $tenantId,
                'homework_ids' => $ids,
            ],
            ['homework_ids' => ArrayParameterType::STRING],
        );

        $result = array_fill_keys($ids, null);

        foreach ($rows as $row) {
            /** @var string $hwId */
            $hwId = $row['homework_id'];
            /** @var string $status */
            $status = $row['status'];
            $result[$hwId] = $status;
        }

        return $result;
    }

    /** @param array<string, mixed> $row */
    private function hydrate(array $row): HomeworkSubmission
    {
        /** @var string $id */
        $id = $row['id'];
        /** @var string $tenantId */
        $tenantId = $row['tenant_id'];
        /** @var string $homeworkId */
        $homeworkId = $row['homework_id'];
        /** @var string $studentId */
        $studentId = $row['student_id'];
        /** @var string|null $responseHtml */
        $responseHtml = $row['response_html'];
        /** @var string $status */
        $status = $row['status'];
        /** @var string|null $submittedAt */
        $submittedAt = $row['submitted_at'];
        /** @var string $createdAt */
        $createdAt = $row['created_at'];
        /** @var string $updatedAt */
        $updatedAt = $row['updated_at'];

        return HomeworkSubmission::reconstitute(
            id: HomeworkSubmissionId::fromString($id),
            tenantId: TenantId::fromString($tenantId),
            homeworkId: HomeworkId::fromString($homeworkId),
            studentId: UserId::fromString($studentId),
            responseHtml: $responseHtml,
            status: SubmissionStatus::from($status),
            submittedAt: $submittedAt !== null ? new DateTimeImmutable($submittedAt) : null,
            createdAt: new DateTimeImmutable($createdAt),
            updatedAt: new DateTimeImmutable($updatedAt),
        );
    }
}
