<?php

declare(strict_types=1);

namespace App\Scolarite\Infrastructure\Api\Controller;

use App\Administration\Domain\Model\User\UserId;
use App\Administration\Infrastructure\Security\SecurityUser;
use App\Scolarite\Application\Command\SaveDraftSubmission\SaveDraftSubmissionCommand;
use App\Scolarite\Application\Command\SaveDraftSubmission\SaveDraftSubmissionHandler;
use App\Scolarite\Application\Command\SubmitHomework\SubmitHomeworkCommand;
use App\Scolarite\Application\Command\SubmitHomework\SubmitHomeworkHandler;
use App\Scolarite\Application\Command\UploadSubmissionAttachment\UploadSubmissionAttachmentCommand;
use App\Scolarite\Application\Command\UploadSubmissionAttachment\UploadSubmissionAttachmentHandler;
use App\Scolarite\Application\Port\StudentClassReader;
use App\Scolarite\Domain\Exception\EleveNonAffecteAuDevoirException;
use App\Scolarite\Domain\Exception\RenduDejaSoumisException;
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\SubmissionAttachment;
use App\Scolarite\Domain\Repository\HomeworkRepository;
use App\Scolarite\Domain\Repository\HomeworkSubmissionRepository;
use App\Scolarite\Domain\Repository\SubmissionAttachmentRepository;
use App\Scolarite\Infrastructure\Security\HomeworkStudentVoter;
use App\Shared\Domain\Tenant\TenantId;

use function array_map;

use DateTimeImmutable;

use function is_string;

use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[IsGranted(HomeworkStudentVoter::VIEW)]
final readonly class StudentSubmissionController
{
    public function __construct(
        private Security $security,
        private SaveDraftSubmissionHandler $saveDraftHandler,
        private SubmitHomeworkHandler $submitHandler,
        private UploadSubmissionAttachmentHandler $uploadHandler,
        private HomeworkRepository $homeworkRepository,
        private HomeworkSubmissionRepository $submissionRepository,
        private SubmissionAttachmentRepository $attachmentRepository,
        private StudentClassReader $studentClassReader,
        private MessageBusInterface $eventBus,
    ) {
    }

    #[Route('/api/me/homework/{id}/submission', name: 'api_student_submission_save_draft', methods: ['POST'])]
    public function saveDraft(string $id, Request $request): JsonResponse
    {
        $user = $this->getSecurityUser();
        $this->assertHomeworkBelongsToStudentClass($user, $id);

        /** @var array{responseHtml?: string|null} $data */
        $data = $request->toArray();

        try {
            $submission = ($this->saveDraftHandler)(new SaveDraftSubmissionCommand(
                tenantId: $user->tenantId(),
                homeworkId: $id,
                studentId: $user->userId(),
                responseHtml: isset($data['responseHtml']) && is_string($data['responseHtml']) ? $data['responseHtml'] : null,
            ));
        } catch (EleveNonAffecteAuDevoirException $e) {
            throw new NotFoundHttpException('Devoir non trouvé.', $e);
        } catch (RenduDejaSoumisException $e) {
            throw new ConflictHttpException($e->getMessage(), $e);
        }

        return new JsonResponse(
            ['data' => $this->serializeSubmission($submission)],
            Response::HTTP_CREATED,
        );
    }

    #[Route('/api/me/homework/{id}/submission/submit', name: 'api_student_submission_submit', methods: ['POST'])]
    public function submit(string $id): JsonResponse
    {
        $user = $this->getSecurityUser();
        $this->assertHomeworkBelongsToStudentClass($user, $id);

        try {
            $submission = ($this->submitHandler)(new SubmitHomeworkCommand(
                tenantId: $user->tenantId(),
                homeworkId: $id,
                studentId: $user->userId(),
            ));
        } catch (EleveNonAffecteAuDevoirException $e) {
            throw new NotFoundHttpException('Devoir non trouvé.', $e);
        } catch (RenduNonTrouveException $e) {
            throw new NotFoundHttpException('Aucun brouillon à soumettre.', $e);
        } catch (RenduDejaSoumisException $e) {
            throw new ConflictHttpException($e->getMessage(), $e);
        }

        foreach ($submission->pullDomainEvents() as $event) {
            $this->eventBus->dispatch($event);
        }

        return new JsonResponse(['data' => $this->serializeSubmission($submission)]);
    }

    #[Route('/api/me/homework/{id}/submission/attachments', name: 'api_student_submission_upload_attachment', methods: ['POST'])]
    public function uploadAttachment(string $id, Request $request): JsonResponse
    {
        $user = $this->getSecurityUser();
        $this->assertHomeworkBelongsToStudentClass($user, $id);

        $tenantId = TenantId::fromString($user->tenantId());
        $homeworkId = HomeworkId::fromString($id);

        $submission = $this->submissionRepository->findByHomeworkAndStudent(
            $homeworkId,
            UserId::fromString($user->userId()),
            $tenantId,
        );

        if ($submission === null) {
            throw new NotFoundHttpException('Aucun brouillon trouvé. Veuillez d\'abord créer un brouillon.');
        }

        if (!$submission->status->estModifiable()) {
            throw new ConflictHttpException('Le rendu a déjà été soumis.');
        }

        $file = $request->files->get('file');

        if ($file === null) {
            throw new BadRequestHttpException('Aucun fichier fourni.');
        }

        /** @var \Symfony\Component\HttpFoundation\File\UploadedFile $file */
        $attachment = ($this->uploadHandler)(new UploadSubmissionAttachmentCommand(
            tenantId: $user->tenantId(),
            submissionId: (string) $submission->id,
            filename: $file->getClientOriginalName(),
            mimeType: $file->getMimeType() ?? 'application/octet-stream',
            fileSize: $file->getSize(),
            tempFilePath: $file->getPathname(),
        ));

        $this->attachmentRepository->save($submission->id, $attachment);

        return new JsonResponse(
            ['data' => $this->serializeAttachment($attachment)],
            Response::HTTP_CREATED,
        );
    }

    #[Route('/api/me/homework/{id}/submission', name: 'api_student_submission_get', methods: ['GET'])]
    public function getSubmission(string $id): JsonResponse
    {
        $user = $this->getSecurityUser();
        $this->assertHomeworkBelongsToStudentClass($user, $id);

        $tenantId = TenantId::fromString($user->tenantId());
        $homeworkId = HomeworkId::fromString($id);

        $submission = $this->submissionRepository->findByHomeworkAndStudent(
            $homeworkId,
            UserId::fromString($user->userId()),
            $tenantId,
        );

        if ($submission === null) {
            return new JsonResponse(['data' => null]);
        }

        $attachments = $this->attachmentRepository->findBySubmissionId($submission->id);

        return new JsonResponse([
            'data' => $this->serializeSubmissionWithAttachments($submission, $attachments),
        ]);
    }

    private function assertHomeworkBelongsToStudentClass(SecurityUser $user, string $homeworkId): void
    {
        $tenantId = TenantId::fromString($user->tenantId());
        $homework = $this->homeworkRepository->findById(HomeworkId::fromString($homeworkId), $tenantId);

        if ($homework === null) {
            throw new NotFoundHttpException('Devoir non trouvé.');
        }

        $classId = $this->studentClassReader->currentClassId($user->userId(), $tenantId);

        if ($classId === null || $classId !== (string) $homework->classId) {
            throw new NotFoundHttpException('Devoir non trouvé.');
        }
    }

    private function getSecurityUser(): SecurityUser
    {
        $user = $this->security->getUser();

        if (!$user instanceof SecurityUser) {
            throw new AccessDeniedHttpException('Authentification requise.');
        }

        return $user;
    }

    /**
     * @return array<string, mixed>
     */
    private function serializeSubmission(HomeworkSubmission $submission): array
    {
        return [
            'id' => (string) $submission->id,
            'homeworkId' => (string) $submission->homeworkId,
            'studentId' => (string) $submission->studentId,
            'responseHtml' => $submission->responseHtml,
            'status' => $submission->status->value,
            'submittedAt' => $submission->submittedAt?->format(DateTimeImmutable::ATOM),
            'createdAt' => $submission->createdAt->format(DateTimeImmutable::ATOM),
            'updatedAt' => $submission->updatedAt->format(DateTimeImmutable::ATOM),
        ];
    }

    /**
     * @param array<SubmissionAttachment> $attachments
     *
     * @return array<string, mixed>
     */
    private function serializeSubmissionWithAttachments(HomeworkSubmission $submission, array $attachments): array
    {
        $data = $this->serializeSubmission($submission);
        $data['attachments'] = array_map($this->serializeAttachment(...), $attachments);

        return $data;
    }

    /**
     * @return array<string, mixed>
     */
    private function serializeAttachment(SubmissionAttachment $attachment): array
    {
        return [
            'id' => (string) $attachment->id,
            'filename' => $attachment->filename,
            'fileSize' => $attachment->fileSize,
            'mimeType' => $attachment->mimeType,
        ];
    }
}
