<?php

namespace App\Controller\OgAgent;

use App\Dto\Input\CommandExecuteInput;
use App\Dto\Input\MultipleClientsInput;
use App\Entity\Client;
use App\Entity\Trace;
use App\Model\ClientStatus;
use App\Model\CommandTypes;
use App\Model\TraceStatus;
use App\Service\Trace\CreateService;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Validator\Exception\ValidatorException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class RunScriptAction extends AbstractOgAgentController
{
    /**
     * @throws TransportExceptionInterface
     * @throws ServerExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ClientExceptionInterface
     */
    public function __invoke(CommandExecuteInput $input, ?Trace $existingTrace = null): JsonResponse
    {
        $clientJobs = [];

        /** @var Client $clientEntity */
        foreach ($input->clients as $clientEntity) {
            /** @var Client $client */
            $client = $clientEntity->getEntity();

            if (!$client->getIp()) {
                throw new BadRequestHttpException('IP is required');
            }

            $data = $this->buildRequestData($client, $input->script);
            $response = $this->executeScript($client, $data);

            if ($this->isErrorResponse($response)) {
                $this->handleError($client, $input, $response);
                continue;
            }

            $jobId = $response['job_id'];
            $clientJobs[(string) '/clients/' . $client->getUuid()] = $jobId;

            $this->handleSuccess($client, $input, $response, $existingTrace);
        }

        return new JsonResponse(data: $clientJobs, status: Response::HTTP_OK);
    }

    private function buildRequestData(Client $client, string $script): array
    {
        $scriptBase64 = base64_encode($script);

        if ($this->isLinuxOrWindows($client)) {
            return [
                'script' => $scriptBase64,
                'client' => 'false'
            ];
        }

        if ($this->isLinuxOrWindowsSession($client)) {
            return [
                'script' => $scriptBase64,
                'client' => 'true'
            ];
        }

        return [
            'nfn' => 'EjecutarScript',
            'scp' => $scriptBase64,
            'ids' => '0'
        ];
    }

    private function isLinuxOrWindows(Client $client): bool
    {
        return $client->getStatus() === ClientStatus::LINUX || $client->getStatus() === ClientStatus::WINDOWS;
    }

    private function isLinuxOrWindowsSession(Client $client): bool
    {
        return $client->getStatus() === ClientStatus::LINUX_SESSION || $client->getStatus() === ClientStatus::WINDOWS_SESSION;
    }

    private function executeScript(Client $client, array $data): array
    {
        return $this->createRequest(
            method: 'POST',
            url: 'https://'.$client->getIp().':8000/opengnsys/EjecutarScript',
            params: [
                'json' => $data,
            ],
            token: $client->getToken(),
        );
    }

    private function isErrorResponse(array $response): bool
    {
        return isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR;
    }

    private function handleError(Client $client, CommandExecuteInput $input, array $response): void
    {
        $this->logger->error('Error running script', [
            'client' => $client->getId(), 
            'error' => $response['error']
        ]);

        if ($input->queue) {
            $inputData = ['script' => $input->script];
            $this->createService->__invoke($client, CommandTypes::RUN_SCRIPT, TraceStatus::PENDING, null, $inputData);
        }
    }

    private function handleSuccess(Client $client, CommandExecuteInput $input, array $response, ?Trace $existingTrace): void
    {
        $this->logger->info('Running script on client', ['client' => $client->getId()]);

        $jobId = $response['job_id'];
        $inputData = ['script' => $input->script];

        $this->entityManager->persist($client);
        $this->entityManager->flush();

        if ($existingTrace) {
            $this->updateExistingTrace($existingTrace, $jobId, $inputData, $client);
        } else {
            $status = ($this->isLinuxOrWindows($client) || $this->isLinuxOrWindowsSession($client)) ? TraceStatus::SENT : TraceStatus::IN_PROGRESS;
            $this->createService->__invoke($client, CommandTypes::RUN_SCRIPT, $status, $jobId, $inputData);
        }
    }

    private function updateExistingTrace(Trace $trace, string $jobId, array $inputData, Client $client): void
    {
        $status = ($this->isLinuxOrWindows($client) || $this->isLinuxOrWindowsSession($client)) ? TraceStatus::SENT : TraceStatus::IN_PROGRESS;
        $trace->setStatus($status);
        $trace->setJobId($jobId);
        $trace->setInput($inputData);
        $this->entityManager->persist($trace);
        $this->entityManager->flush();
    }
}