<?php

declare(strict_types=1);

namespace App\Controller\OgAgent\Webhook;

use App\Controller\OgRepository\Image\CreateAuxFilesAction;
use App\Entity\Client;
use App\Entity\Image;
use App\Entity\ImageImageRepository;
use App\Entity\OperativeSystem;
use App\Entity\Partition;
use App\Entity\Software;
use App\Entity\SoftwareProfile;
use App\Entity\Trace;
use App\Model\ClientStatus;
use App\Model\CommandTypes;
use App\Model\ImageStatus;
use App\Model\SoftwareTypes;
use App\Model\TraceStatus;
use App\Service\CreatePartitionService;
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\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Annotation\Route;
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;

#[AsController]
class StatusController extends AbstractController
{
    const string CREATE_IMAGE = 'RESPUESTA_CrearImagen';
    const string CREATE_IMAGE_GIT = 'RESPUESTA_CrearImagenGit';
    const string UPDATE_IMAGE_GIT = 'RESPUESTA_ActualizarImagenGit';
    const string RESTORE_IMAGE = 'RESPUESTA_RestaurarImagen';
    const string RESTORE_IMAGE_GIT = 'RESPUESTA_RestaurarImagenGit';
    const string CONFIGURE_IMAGE = 'RESPUESTA_Configurar';
    const string HARDWARE_INVENTORY = 'RESPUESTA_InventarioHardware';
    const string SOFTWARE_INVENTORY = 'RESPUESTA_InventarioSoftware';
    const string RUN_SCRIPT = 'RESPUESTA_EjecutarScript';

    public function __construct(
        protected readonly EntityManagerInterface $entityManager,
        public readonly CreateAuxFilesAction $createAuxFilesAction,
        protected readonly CreatePartitionService $createPartitionService,
        protected readonly LoggerInterface $logger,
        protected readonly CreateService $createService,
    )
    {
    }

    /**
     * @throws TransportExceptionInterface
     * @throws ServerExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ClientExceptionInterface
     */
    #[Route('/opengnsys/rest/clients/status/webhook', methods: ['POST'])]
    public function index(Request $request): JsonResponse
    {
        $data = $request->toArray();

        $this->logger->info('Webhook data received', $data);

        if (isset($data['iph']) && isset($data['timestamp'])) {
            $client = $this->entityManager->getRepository(Client::class)->findOneBy(['ip' => $data['iph']]);
            if (!$client) {
                $this->logger->error('Client not found', $data);
                return new JsonResponse(['message' => 'Client not found'], Response::HTTP_NOT_FOUND);
            }

            $client->setUpdatedAt(new \DateTime());
            $this->entityManager->persist($client);
            $this->entityManager->flush();
        }

        if (isset($data['progress'])){
            $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);
            if ($trace){
                $trace->setProgress((int)($data['progress'] * 100));
                $this->entityManager->persist($trace);
                $this->entityManager->flush();
            }
        }
        
        if (isset($data['nfn']) && $data['nfn'] === self::CREATE_IMAGE_GIT) {
            $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);

            if (!$trace) {
                $this->logger->error('Trace not found', $data);
                return new JsonResponse(['message' => 'Trace not found'], Response::HTTP_NOT_FOUND);
            }
            
            if ($data['res'] === 1) {
                $trace->setStatus(TraceStatus::SUCCESS);
                $trace->setFinishedAt(new \DateTime());
            } else {
                $trace->setStatus(TraceStatus::FAILED);
                $trace->setFinishedAt(new \DateTime());
                $trace->setOutput($data['der']);
            }

            $client = $trace->getClient();
            $client->setStatus(ClientStatus::OG_LIVE);
            $this->entityManager->persist($client);
            $this->entityManager->persist($trace);
            $this->entityManager->flush();
            $this->logger->info('Git image creation completed.', ['job_id' => $data['job_id'], 'success' => $data['res'] === 1]);
        }

        if (isset($data['nfn']) && $data['nfn'] === self::UPDATE_IMAGE_GIT) {
            $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);

            if (!$trace) {
                $this->logger->error('Trace not found', $data);
                return new JsonResponse(['message' => 'Trace not found'], Response::HTTP_NOT_FOUND);
            }
            
            if ($data['res'] === 1) {
                $trace->setStatus(TraceStatus::SUCCESS);
                $trace->setFinishedAt(new \DateTime());
            } else {
                $trace->setStatus(TraceStatus::FAILED);
                $trace->setFinishedAt(new \DateTime());
                $trace->setOutput($data['der']);
            }

            $client = $trace->getClient();
            $client->setStatus(ClientStatus::OG_LIVE);
            $this->entityManager->persist($client);
            $this->entityManager->persist($trace);
            $this->entityManager->flush();
            $this->logger->info('Git image update completed.', ['job_id' => $data['job_id'], 'success' => $data['res'] === 1]);
        }

        if (isset($data['nfn']) && $data['nfn'] === self::CREATE_IMAGE) {
            $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);
            /** @var ImageImageRepository $imageImageRepository */
            $imageImageRepository = $this->entityManager->getRepository(ImageImageRepository::class)->findOneBy(['uuid' => $data['idi']]);

            if (!$imageImageRepository) {
                $this->logger->error('Image not found', $data);
                return new JsonResponse(['message' => 'Image not found'], Response::HTTP_NOT_FOUND);
            }

            if (!$trace) {
                $this->logger->error('Trace not found', $data);
                return new JsonResponse(['message' => 'Trace not found'], Response::HTTP_NOT_FOUND);
            }

            if ($data['res'] === 1) {
                $trace->setStatus(TraceStatus::SUCCESS);
                $trace->setFinishedAt(new \DateTime());
                $imageImageRepository->setStatus(ImageStatus::AUX_FILES_PENDING);
                $imageImageRepository->setCreated(true);
                $this->entityManager->persist($imageImageRepository);

                $this->logger->info('Start partition creation. ', ['image' => (string) $imageImageRepository->getUuid()]);
                if (isset($data['cfg'])) {
                    $this->createPartitionService->__invoke($data, $imageImageRepository->getImage()->getClient());
                }
                $this->logger->info('Starting software profile creation. ', ['image' => (string) $imageImageRepository->getUuid()]);
                $this->createSoftwareProfile($data['inv_sft'], $imageImageRepository);
                $this->logger->info('Start aux files ogrepo API ', ['image' => (string) $imageImageRepository->getUuid()]);

                try {
                    $this->createAuxFilesAction->__invoke($imageImageRepository);
                } catch (\Exception $e) {
                    $this->logger->error('Error creating aux files', ['image' => (string) $imageImageRepository->getUuid(), 'error' => $e->getMessage()]);
                }

                $this->logger->info('End aux files ogrepo API ', ['image' => (string) $imageImageRepository->getUuid()]);
            } else {
                $trace->setStatus(TraceStatus::FAILED);
                $trace->setFinishedAt(new \DateTime());
                $trace->setOutput($data['der']);
                $imageImageRepository->setCreated(false);
                $imageImageRepository->setStatus(ImageStatus::FAILED);
                $this->logger->error('Image updated failed', $data);
            }

            $this->entityManager->persist($imageImageRepository);
            $client = $trace->getClient();
            $client->setStatus(ClientStatus::OG_LIVE);
            $this->entityManager->persist($client);
            $this->entityManager->persist($trace);
            $this->entityManager->flush();
            $this->logger->info('Image updated. Success.', ['image' => (string) $imageImageRepository->getUuid()]);
        }

        if (isset($data['nfn']) && ($data['nfn'] === self::RESTORE_IMAGE || $data['nfn'] === self::CONFIGURE_IMAGE || $data['nfn'] === self::RESTORE_IMAGE_GIT)) {
            $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);
            $client = $trace->getClient();

            if ($data['res'] === 1) {
                $trace->setStatus(TraceStatus::SUCCESS);
                $trace->setFinishedAt(new \DateTime());
                if (isset($data['cfg'])) {
                    $this->createPartitionService->__invoke($data,$client);
                }
            } else {
                $trace->setStatus(TraceStatus::FAILED);
                $trace->setFinishedAt(new \DateTime());
                $trace->setOutput($data['der']);
            }

            $client->setStatus(ClientStatus::OG_LIVE);
            $this->entityManager->persist($client);
            $this->entityManager->persist($trace);

            if ($data['nfn'] === self::RESTORE_IMAGE) {
                $partitionData = json_decode(json_encode($trace->getInput()), true);

                $numDisk = (int) $partitionData['numDisk'] ?? null;
                $numPartition = (int) $partitionData['numPartition'] ?? null;

                $imageImageRepository = $this->entityManager->getRepository(ImageImageRepository::class)->findOneBy(['uuid' => $partitionData['image']]);

                $partition = $this->entityManager->getRepository(Partition::class)
                    ->findOneBy(['diskNumber' => $numDisk, 'partitionNumber' => $numPartition, 'client' => $client]);

                if ($partition) {
                    $partition->setImage($imageImageRepository);
                    $this->entityManager->persist($partition);
                }
            }

            $this->entityManager->flush();
        }

        if (isset($data['nfn']) && $data['nfn'] === self::RUN_SCRIPT) {
            $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);
            if ($trace) {
                $trace->setStatus(TraceStatus::SUCCESS);
                $trace->setFinishedAt(new \DateTime());
                $this->entityManager->persist($trace);
                $this->entityManager->flush();
            }
        }

        if (isset($data['nfn']) && $data['nfn'] === self::HARDWARE_INVENTORY) {
            $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);
            if ($trace) {
                $client = $trace->getClient();

                $trace->setStatus(TraceStatus::SUCCESS);
                $trace->setFinishedAt(new \DateTime());
                $this->entityManager->persist($trace);

                //$client->setHardwareProfile();

                $this->entityManager->flush();
            }
        }

        if (isset($data['nfn']) && $data['nfn'] === self::SOFTWARE_INVENTORY) {
            $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);

            if ($trace) {
                $client = $trace->getClient();
                $dataInput = json_decode(json_encode($trace->getInput()), true);
                $imageUuid = $dataInput['image'] ?? null;

                $trace->setStatus(TraceStatus::SUCCESS);
                $trace->setFinishedAt(new \DateTime());
                $this->entityManager->persist($trace);

                $image = $this->entityManager->getRepository(ImageImageRepository::class)->findOneBy(['uuid' => $imageUuid]);
                $this->createSoftwareProfile($data['contents'], $image);

                $this->entityManager->flush();
            }
        }

        return new JsonResponse(data: 'Webhook finished', status: Response::HTTP_OK);
    }

    public function createSoftwareProfile(string $base64Data, ?ImageImageRepository $imageImageRepository = null): void
    {
        $decodedData = base64_decode($base64Data);
        $this->logger->info('Software profile decoded', ['data' => '']);
        $softwareList = array_map('trim', explode("\n", $decodedData));
        $softwareList = array_filter($softwareList);
        $existingSoftware = $this->entityManager->getRepository(Software::class)->findBy(['name' => $softwareList]);
        $existingNames = array_map(fn($software) => $software->getName(), $existingSoftware);
        $newSoftwareNames = array_diff($softwareList, $existingNames);

        foreach ($newSoftwareNames as $software) {
            $softwareEntity = new Software();
            $softwareEntity->setName($software);
            $softwareEntity->setType(SoftwareTypes::APPLICATION);
            $this->entityManager->persist($softwareEntity);
        }

        $image = $imageImageRepository?->getImage();

        $softwareProfile = new SoftwareProfile();
        $softwareProfile->setDescription('SW Profile - ' . $image->getClient()->getName());
        $softwareProfile->setOrganizationalUnit($image->getClient()->getOrganizationalUnit());

        foreach ($existingSoftware as $softwareEntity) {
            $softwareEntity->addSoftwareProfile($softwareProfile);
        }

        $image->setSoftwareProfile($softwareProfile);

        $this->entityManager->persist($image);
        $this->entityManager->persist($imageImageRepository);
        $this->entityManager->persist($softwareProfile);
        $this->entityManager->flush();
    }
}
