<?php declare(strict_types=1);

namespace App\Controller\Team;

use App\Controller\BaseController;
use App\Entity\Contest;
use App\Entity\ContestProblem;
use App\Service\AuthorizedUserService;
use App\Service\ConfigurationService;
use App\Service\DOMJudgeService;
use App\Service\EventLogService;
use App\Service\StatisticsService;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[IsGranted('ROLE_TEAM')]
#[IsGranted(
    new Expression('user.getTeam() !== null'),
    message: 'You do not have a team associated with your account.'
)]
#[Route(path: '/team')]
class ProblemController extends BaseController
{
    public function __construct(
        protected readonly AuthorizedUserService $authService,
        DOMJudgeService $dj,
        protected readonly ConfigurationService $config,
        protected readonly StatisticsService $stats,
        protected readonly EventLogService $eventLogService,
        EntityManagerInterface $em,
        KernelInterface $kernel,
    ) {
        parent::__construct($em, $eventLogService, $dj, $kernel);
    }

    /**
     * @throws NonUniqueResultException
     */
    #[Route(path: '/problems', name: 'team_problems')]
    public function problemsAction(): Response
    {
        $teamId = $this->authService->getUser()->getTeam()->getTeamid();
        return $this->render('team/problems.html.twig',
            $this->dj->getTwigDataForProblemsAction($this->stats, $teamId));
    }


    #[Route(path: '/problems/{probId}/statement', name: 'team_problem_statement')]
    public function problemStatementAction(string $probId): StreamedResponse
    {
        return $this->getBinaryFile($probId, function (
            string $probId,
            Contest $contest,
            ContestProblem $contestProblem
        ) {
            $problem = $contestProblem->getProblem();

            try {
                return $problem->getProblemStatementStreamedResponse();
            } catch (BadRequestHttpException $e) {
                $this->addFlash('danger', $e->getMessage());
                return $this->redirectToRoute('team_problems');
            }
        });
    }

    /**
     * @throws NonUniqueResultException
     */
    #[Route(path: '/{probId}/attachment/{attachmentId<\d+>}', name: 'team_problem_attachment')]
    public function attachmentAction(string $probId, int $attachmentId): StreamedResponse
    {
        return $this->getBinaryFile($probId, fn(
            string $probId,
            Contest $contest,
            ContestProblem $contestProblem
        ) => $this->dj->getAttachmentStreamedResponse($contestProblem, $attachmentId));
    }

    #[Route(path: '/{probId}/samples.zip', name: 'team_problem_sample_zip')]
    public function sampleZipAction(string $probId): StreamedResponse
    {
        return $this->getBinaryFile($probId, function (string $probId, Contest $contest, ContestProblem $contestProblem) {
            return $this->dj->getSamplesZipStreamedResponse($contestProblem);
        });
    }

    /**
     * Get a binary file for the given problem ID using the given callable.
     *
     * Shared code between testcases, problem text and attachments.
     */
    protected function getBinaryFile(string $probId, callable $response): StreamedResponse
    {
        $user    = $this->authService->getUser();
        $contest = $this->dj->getCurrentContest($user->getTeam()->getTeamid());
        if (!$contest || !$contest->getFreezeData()->started()) {
            throw new NotFoundHttpException(sprintf('Problem p%d not found or not available', $probId));
        }
        $contestProblem = $this->em->getRepository(ContestProblem::class)->findByProblemAndContest($contest, $probId);
        if (!$contestProblem) {
            throw new NotFoundHttpException(sprintf('Problem p%d not found or not available', $probId));
        }

        return $response($probId, $contest, $contestProblem);
    }
}
