<?php

namespace KupShop\GraphQLBundle\Controller;

use GraphQL\Executor\ExecutionResult;
use GraphQL\GraphQL;
use KupShop\AdminBundle\Util\LegacyAdminCredentials;
use KupShop\GraphQLBundle\Util\SchemaFactory;
use KupShop\KupShopBundle\Context\PosContext;
use KupShop\KupShopBundle\Routing\AdminRoute;
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
use KupShop\KupShopBundle\Util\RequestUtil;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use TheCodingMachine\GraphQLite\Context\Context;

class AdminController
{
    public function __construct(
        private SchemaFactory $schemaFactory,
        private SentryLogger $sentryLogger,
        private LoggerInterface $logger,
        public PosContext $posContext,
    ) {
    }

    /**
     * @AdminRoute("/graphql{slash}", requirements={"slash": "\/$"}, defaults={"slash": "/"})
     */
    public function graphQLAdmin(Request $request, LegacyAdminCredentials $legacyAdminCredentials): Response
    {
        if (!findModule(\Modules::GRAPHQL, \Modules::SUB_GRAPHQL_ADMIN)) {
            throw new NotFoundHttpException();
        }

        increaseMaxExecutionTime(120);
        increaseMemoryLimit(512);

        // CORS check - return 200
        if ($request->isMethod('OPTIONS')) {
            return $this->getAPIResponse([]);
        }

        $token = $request->headers->get('X-Access-Token');

        if (!$legacyAdminCredentials->isLogged() && (!$token || !$legacyAdminCredentials->loginByHash($token))) {
            $this->log('Unauthorized access', [
                'token' => $token,
                'uri' => $request->getUri(),
                'headers' => $request->headers->all(),
            ]);

            return $this->getAPIResponse(['errors' => ['message' => 'Unauthorized access']], 401);
        }

        $legacyAdminCredentials->setAdminGlobalVars();

        $admin = LegacyAdminCredentials::getAdminById($legacyAdminCredentials->getAdminID());

        if (!findRight('API')) {
            return $this->getAPIResponse(['errors' => ['message' => 'Used access token does not have access to the API']], 403);
        }

        $schema = $this->schemaFactory->createAdminSchema();

        $input = json_decode($request->getContent(), true);

        if (($query = ($input['query'] ?? null)) === null) {
            return $this->getAPIResponse(['errors' => ['message' => 'Invalid request! Missing "query" param']], 400);
        }

        $variableValues = $input['variables'] ?? null;

        $result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);
        $output = $result->toArray();

        $this->captureGraphQLRequest($input, $admin);
        $this->captureGraphQLErrors($result);

        return $this->getAPIResponse($output);
    }

    private function captureGraphQLRequest(array $input, ?array $admin = null): void
    {
        $this->log('API Request', [
            'input' => $input,
            'admin' => ['id' => $admin['id'], 'email' => $admin['email'], 'login' => $admin['login'], 'name' => $admin['name'] ?? ''],
        ]);
    }

    private function captureGraphQLErrors(ExecutionResult $result): void
    {
        foreach ($result->errors ?? [] as $error) {
            // pokud je chyba client safe, tak ji neloguju do sentry
            if ($error->isClientSafe()) {
                continue;
            }

            if (isLocalDevelopment()) {
                throw $error;
            }

            $this->sentryLogger->captureException($error);
        }
    }

    private function log(string $message, array $data): void
    {
        if (isLocalDevelopment()) {
            return;
        }

        $this->logger->notice("[GraphQL] {$message}", $data);
    }

    private function getAPIResponse(array $data, int $code = 200): JsonResponse
    {
        return new JsonResponse(
            $data,
            $code,
            [
                'Content-Type' => 'application/json',
                'Access-Control-Allow-Origin' => '*',
                'Access-Control-Allow-Headers' => '*',
            ]
        );
    }

    /**
     * @AdminRoute("/pos/graphql{slash}", requirements={"slash": "\/$"}, defaults={"slash": "/"})
     */
    public function graphQLPos(Request $request, LegacyAdminCredentials $legacyAdminCredentials, RequestUtil $requestUtil): Response
    {
        if (!findModule(\Modules::NEW_POS)) {
            throw new NotFoundHttpException();
        }

        // Checks if the request has a pos ID in the header and activates context
        if ($idPos = PosContext::getIdFromRequest($request)) {
            $this->posContext->activate($idPos);
        }

        // Checks the API versions, and if they differ, returns 426 Upgrade Required.
        if ($this->posContext->getApiVersion() !== PosContext::getVersionFromRequest($request)) {
            return $this->getAPIResponse([], 426);
        }

        // CORS check - return 200
        if ($request->isMethod('OPTIONS')) {
            return $this->getAPIResponse([]);
        }

        $token = $request->headers->get('X-Access-Token');
        if (!$legacyAdminCredentials->isLogged() && (!$token || !$legacyAdminCredentials->loginByHash($token))) {
            return $this->getAPIResponse(['errors' => ['message' => 'Unauthorized access']], 401);
        }

        $legacyAdminCredentials->setAdminGlobalVars();

        if (!findRight('POS_API')) {
            return $this->getAPIResponse(['errors' => ['message' => 'User does not have access']], 403);
        }

        $schema = $this->schemaFactory->createPosSchema();
        $input = json_decode($request->getContent(), true);
        if (!$input) {
            return $this->getAPIResponse(['errors' => ['message' => 'Unprocessable Entity - empty input']], 422);
        }

        if (preg_match('/^\s*(query|mutation)\s*(\w+)/i', $input['query'], $matches)) {
            $requestUtil->addTransactionInfo($request, ['query' => $matches[2]]);
        }

        $result = GraphQL::executeQuery($schema, $input['query'], null, new Context(), $input['variables'] ?? null);
        $output = $result->toArray();

        return $this->getAPIResponse($output);
    }
}
