<?php

declare(strict_types=1);

namespace App\Controller\OgAgent;

use App\Controller\OgRepository\Git\CreateRepositoryAction;
use App\Entity\Client;
use App\Entity\Command;
use App\Entity\GitRepository;
use App\Entity\Image;
use App\Entity\ImageImageRepository;
use App\Entity\ImageRepository;
use App\Entity\Partition;
use App\Entity\Trace;
use App\Model\ClientStatus;
use App\Model\CommandTypes;
use App\Model\ImageStatus;
use App\Model\PartitionTypes;
use App\Model\TraceStatus;
use App\Service\Trace\CreateService;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
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\Routing\Attribute\Route;
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 CreateImageAction extends AbstractOgAgentController
{
    /**
     * @throws TransportExceptionInterface
     * @throws ServerExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ClientExceptionInterface
     */
    public function __invoke(bool $queue, Image $image, ?Partition $partition = null, ?Client $client = null, ?string $gitRepositoryName = null, ?Trace $existingTrace = null): JsonResponse
    {
        $client = $client ?? $image->getClient();

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

        $partitionInfo = [];

        if ($partition) {
            $partitionInfo["numPartition"] = $partition->getPartitionNumber();
            $partitionInfo["numDisk"] = $partition->getDiskNumber();
            $partitionInfo["partitionCode"] = $partition->getPartitionCode();
            $partitionInfo["filesystem"] = $partition->getFilesystem();
            $partitionInfo["osName"] = $partition->getOperativeSystem()?->getName();
            $image->setPartitionInfo(json_encode($partitionInfo));
        } else {
            $partitionInfo = json_decode($image->getPartitionInfo(), true);
        }

        if ($image->getType() === 'monolithic') {
            $repository = $image->getClient()->getRepository();
            $latestImageRepo = $this->entityManager->getRepository(ImageImageRepository::class)->findLatestVersionByImageAndRepository($image, $repository);

            $imageImageRepository = new ImageImageRepository();
            $imageImageRepository->setName($image->getName().'_v'.($latestImageRepo ? $latestImageRepo->getVersion() + 1 : 1));
            $imageImageRepository->setImage($image);
            $imageImageRepository->setRepository($repository);
            $imageImageRepository->setStatus(ImageStatus::IN_PROGRESS);
            $imageImageRepository->setVersion($latestImageRepo ? $latestImageRepo->getVersion() + 1 : 1);
            $imageImageRepository->setPartitionInfo(json_encode($partitionInfo));

            $this->entityManager->persist($imageImageRepository);

            return $this->createMonolithicImage($imageImageRepository, $partitionInfo, $image, $repository, $client, $queue, $existingTrace);
        } else {
            $repository = $image->getClient()->getRepository();
            
            return $this->createGitImage($image, $partitionInfo, $repository, $queue, $gitRepositoryName, $existingTrace);
        }
    }

    /**
     * @throws TransportExceptionInterface
     * @throws ServerExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ClientExceptionInterface
     */
    public function createMonolithicImage(
        ImageImageRepository $imageImageRepository,
        array $partitionInfo,
        Image $image,
        ImageRepository $repository,
        ?Client $client = null,
        bool $queue = false,
        ?Trace $existingTrace = null
    ): JsonResponse
    {
        if (!isset($partitionInfo['numDisk'], $partitionInfo['numPartition'], $partitionInfo['partitionCode'], $partitionInfo['filesystem'])) {
            throw new BadRequestHttpException('Missing required partition information');
        }

        $client = $client ?? $image->getClient();
        if (!$client->getIp() || !$client->getToken()) {
            throw new BadRequestHttpException('Client IP or token is missing');
        }

        $data = [
            'dsk' => (string) $partitionInfo['numDisk'],
            'par' => (string) $partitionInfo['numPartition'],
            'cpt' => null,
            'idi' => $imageImageRepository->getUuid(),
            'nci' => $image->getName().'_v'.$imageImageRepository->getVersion(),
            'ipr' => $repository->getIp(),
            'nfn' => 'CrearImagen',
            'ids' => '0'
        ];

        $partitionTypes = PartitionTypes::getPartitionTypes();

        $partitionCode = $partitionInfo['partitionCode'];
        $cptKey = array_search($partitionCode, array_column($partitionTypes, 'name'), true);

        if ($cptKey !== false) {
            $keys = array_keys($partitionTypes);
            $partitionTypeCode = $keys[$cptKey];

            $data['cpt'] = dechex($partitionTypeCode);
        } else {
            throw new BadRequestHttpException("Invalid partition code: {$partitionCode}");
        }

        $this->logger->info('Creating image', ['image' => $image->getId()]);

        try {
            $response = $this->createRequest(
                method: 'POST',
                url: 'https://'.$client->getIp().':8000/opengnsys/CrearImagen',
                params: [
                    'json' => $data,
                ],
                token: $client->getToken(),
            );

            $this->logger->info('Creating image', ['image' => $imageImageRepository->getName(), 'repository' => $repository->getIp()]);

            if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) {
                if ($queue) {
                    $inputData = [
                        'method' => 'CrearImagen',
                        'type' => 'monolithic',
                        'client' => $client->getUuid(),
                        'image' => $image->getUuid(),
                        'partitionCode' => $partitionInfo['partitionCode'],
                        'partitionType' => $partitionInfo['filesystem'],
                        'partitionNumber' => $partitionInfo['numPartition'],
                        'diskNumber' => $partitionInfo['numDisk'],
                        'repository' => $repository->getIp(),
                        'name' => $image->getName().'_v'.$imageImageRepository->getVersion(),
                    ];
                    $this->createService->__invoke($client, CommandTypes::CREATE_IMAGE, TraceStatus::PENDING, null, $inputData);
                    return new JsonResponse(data: [], status: Response::HTTP_OK);
                }

                throw new BadRequestHttpException('Error creating image: ' . ($response['message'] ?? 'Unknown error'));
            }

            if (!isset($response['job_id'])) {
                throw new BadRequestHttpException('No job ID received from server');
            }

            $jobId = $response['job_id'];

            try {
                $client->setStatus(ClientStatus::BUSY);
                $imageImageRepository->setStatus(ImageStatus::IN_PROGRESS);
                
                $this->entityManager->persist($client);
                $this->entityManager->persist($imageImageRepository);
                $this->entityManager->flush();

                $inputData = [
                    'method' => 'CrearImagen',
                    'type' => 'monolithic',
                    'client' => $client->getUuid(),
                    'image' => $image->getUuid(),
                    'partitionCode' => $partitionInfo['partitionCode'],
                    'partitionType' => $partitionInfo['filesystem'],
                    'partitionNumber' => $partitionInfo['numPartition'],
                    'diskNumber' => $partitionInfo['numDisk'],
                    'repository' => $repository->getIp(),
                    'name' => $image->getName().'_v'.$imageImageRepository->getVersion(),
                ];

                if ($existingTrace) {
                    $existingTrace->setStatus(TraceStatus::IN_PROGRESS);
                    $existingTrace->setJobId($jobId);
                    $existingTrace->setInput($inputData);
                    $this->entityManager->persist($existingTrace);
                    $this->entityManager->flush();
                } else {
                    $this->createService->__invoke(
                        $image->getClient(), 
                        CommandTypes::CREATE_IMAGE, 
                        TraceStatus::IN_PROGRESS, 
                        $jobId, 
                        $inputData
                    );
                }

                return new JsonResponse(data: ['/clients/' . $client->getUuid() => $jobId], status: Response::HTTP_OK);
            } catch (Exception $e) {
                $client->setStatus(ClientStatus::OG_LIVE);
                $this->entityManager->persist($client);
                $this->entityManager->flush();
                throw $e;
            }
        } catch (Exception $e) {
            $this->logger->error('Error in monolithic image creation process', [
                'repository' => $repository->getId(),
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            return new JsonResponse(
                data: ['error' => $e->getMessage()],
                status: Response::HTTP_INTERNAL_SERVER_ERROR
            );
        }
    }

    public function createGitImage(
        Image $image,
        array $partitionInfo,
        ImageRepository $repository,
        bool $queue = false,
        ?string $gitRepositoryName = null,
        ?Trace $existingTrace = null
    ): JsonResponse
    {
        if (!isset($partitionInfo['numDisk'], $partitionInfo['numPartition'], $partitionInfo['partitionCode'], $partitionInfo['filesystem'])) {
            throw new BadRequestHttpException('Missing required partition information');
        }

        $client = $image->getClient();
        if (!$client->getIp() || !$client->getToken()) {
            throw new BadRequestHttpException('Client IP or token is missing');
        }

        try {
            $data = [
                'dsk' => (string) $partitionInfo['numDisk'],
                'par' => (string) $partitionInfo['numPartition'],
                'cpt' => null,
                'idi' => $gitRepositoryName ?: $image->getUuid(),
                'nci' => $gitRepositoryName ?: $image->getName(),
                'ipr' => $repository->getIp(),
                'nfn' => 'CrearImagenGit',
                'tag' => '',
                'ids' => '0'
            ];

            $partitionTypes = PartitionTypes::getPartitionTypes();
            $partitionCode = $partitionInfo['partitionCode'];
            $cptKey = array_search($partitionCode, array_column($partitionTypes, 'name'), true);

            if ($cptKey !== false) {
                $keys = array_keys($partitionTypes);
                $partitionTypeCode = $keys[$cptKey];
                $data['cpt'] = dechex($partitionTypeCode);
            } else {
                throw new BadRequestHttpException("Invalid partition code: {$partitionCode}");
            }
            
            //$this->sshKeyAction->__invoke($repository, $client);

            $response = $this->createRequest(
                method: 'POST',
                url: 'https://'.$client->getIp().':8000/opengnsys/CrearImagenGit',
                params: [
                    'json' => $data,
                ],
                token: $client->getToken(),
            );

            if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) {
                if ($queue) {
                    $inputData = [
                        'method' => 'CrearImagenGit',
                        'type' => 'git',
                        'client' => $client->getUuid(),
                        'image' => $image->getUuid(),
                        'partitionCode' => $partitionInfo['partitionCode'],
                        'partitionType' => $partitionInfo['filesystem'],
                        'partitionNumber' => $partitionInfo['numPartition'],
                        'diskNumber' => $partitionInfo['numDisk'],
                        'repository' => $repository->getIp(),
                        'name' => $image->getName(),
                    ];

                    $this->createService->__invoke($client, CommandTypes::CREATE_IMAGE_GIT, TraceStatus::PENDING, null, $inputData);
                    return new JsonResponse(data: [], status: Response::HTTP_OK);
                }

                throw new BadRequestHttpException('Error creating image: ' . ($response['message'] ?? 'Unknown error'));
            }

            if (!isset($response['job_id'])) {
                throw new BadRequestHttpException('No job ID received from server');
            }

            $jobId = $response['job_id'];
            
            try {
                $client->setStatus(ClientStatus::BUSY);
                
                $this->entityManager->persist($client);
                $this->entityManager->flush();

                $inputData = [
                    'method' => 'CrearImagenGit',
                    'type' => 'git',
                    'client' => $client->getUuid(),
                    'image' => $image->getUuid(),
                    'partitionCode' => $partitionInfo['partitionCode'],
                    'partitionType' => $partitionInfo['filesystem'],
                    'partitionNumber' => $partitionInfo['numPartition'],
                    'diskNumber' => $partitionInfo['numDisk'],
                    'repository' => $repository->getIp(),
                    'name' => $image->getName(),
                ];

                if ($existingTrace) {
                    $existingTrace->setStatus(TraceStatus::IN_PROGRESS);
                    $existingTrace->setJobId($jobId);
                    $existingTrace->setInput($inputData);
                    $this->entityManager->persist($existingTrace);
                    $this->entityManager->flush();
                } else {
                    $this->createService->__invoke(
                        $image->getClient(), 
                        CommandTypes::CREATE_IMAGE_GIT, 
                        TraceStatus::IN_PROGRESS, 
                        $jobId, 
                        $inputData
                    );
                }

                return new JsonResponse(data: ['/clients/' . $client->getUuid() => $jobId], status: Response::HTTP_OK);
            } catch (Exception $e) {
                $client->setStatus(ClientStatus::OG_LIVE);
                $this->entityManager->persist($client);
                $this->entityManager->flush();
                throw $e;
            }

        } catch (Exception $e) {
            $this->logger->error('Error in git image creation process', [
                'repository' => $repository->getId(),
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            return new JsonResponse(
                data: ['error' => $e->getMessage()],
                status: Response::HTTP_INTERNAL_SERVER_ERROR
            );
        }
    }
}
