<?php

declare(strict_types=1);

namespace App\Command;

use App\Controller\OgAgent\CreateImageAction;
use App\Controller\OgAgent\DeployImageAction;
use App\Controller\OgAgent\PartitionAssistantAction;
use App\Controller\OgAgent\RunScriptAction;
use App\Dto\Input\CommandExecuteInput;
use App\Dto\Input\DeployImageInput;
use App\Dto\Input\PartitionInput;
use App\Dto\Input\PartitionPostInput;
use App\Dto\Output\ClientOutput;
use App\Entity\Client;
use App\Entity\Image;
use App\Entity\ImageImageRepository;
use App\Entity\Partition;
use App\Entity\Trace;
use App\Model\CommandTypes;
use App\Model\TraceStatus;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(name: 'opengnsys:execute-pending-traces', description: 'Execute pending traces')]
class ExecutePendingTracesCommand extends Command
{
    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly CreateImageAction $createImageAction,
        private readonly DeployImageAction $deployImageAction,
        private readonly PartitionAssistantAction $partitionAssistantAction,
        private readonly RunScriptAction $runScriptAction
    ) {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $startTime = microtime(true);

        $traces = $this->entityManager->getRepository(Trace::class)
            ->createQueryBuilder('t')
            ->select('t')
            ->where('t.status = :status')
            ->setParameter('status', TraceStatus::PENDING)
            ->andWhere('t.id IN (
                SELECT MIN(t2.id) 
                FROM App\Entity\Trace t2 
                WHERE t2.status = :status 
                GROUP BY t2.client
            )')
            ->orderBy('t.id', 'ASC')
            ->getQuery()
            ->getResult();

        $count = count($traces);
        $io->info("Found $count pending trace(s) - processing the latest one");

        $processedCount = 0;
        $errorCount = 0;

        foreach ($traces as $trace) {
            try {
                $io->info("Processing trace {$trace->getUuid()} with command: {$trace->getCommand()}");
                
                $result = $this->executeTrace($trace);
                
                if ($result) {
                    $processedCount++;
                    $io->success("Successfully processed trace {$trace->getUuid()}");
                } else {
                    $errorCount++;
                    $io->error("Failed to process trace {$trace->getUuid()}");
                }
            } catch (\Exception $e) {
                $errorCount++;
                $io->error("Error processing trace {$trace->getUuid()}: " . $e->getMessage());
                
                $trace->setStatus(TraceStatus::FAILED);
                $trace->setOutput($e->getMessage());
                $trace->setFinishedAt(new \DateTime());
                $this->entityManager->persist($trace);
            }
        }

        $this->entityManager->flush();

        $executionTime = microtime(true) - $startTime;
        $io->success("Processed $processedCount traces successfully, $errorCount failed");
        $io->note("Execution time: " . round($executionTime, 3) . "s");

        return Command::SUCCESS;
    }

    private function executeTrace(Trace $trace): bool
    {
        $command = $trace->getCommand();
        $input = $trace->getInput() ?? [];
        $client = $trace->getClient();

        if (!$client) {
            throw new \Exception("No client associated with trace");
        }

        $trace->setExecutedAt(new \DateTime());
        $this->entityManager->persist($trace);
        $this->entityManager->flush();

        try {
            switch ($command) {
                case CommandTypes::CREATE_IMAGE:
                    return $this->executeCreateImage($trace, $input);
                
                case CommandTypes::DEPLOY_IMAGE:
                    return $this->executeDeployImage($trace, $input);
                
                case CommandTypes::PARTITION_AND_FORMAT:
                    return $this->executePartitionAssistant($trace, $input);
                
                case CommandTypes::RUN_SCRIPT:
                    return $this->executeRunScript($trace, $input);
                
                default:
                    throw new \Exception("Unsupported command type: $command");
            }
        } catch (\Exception $e) {
            $trace->setStatus(TraceStatus::FAILED);
            $trace->setOutput($e->getMessage());
            $trace->setFinishedAt(new \DateTime());
            $this->entityManager->persist($trace);
            $this->entityManager->flush();
            
            throw $e;
        }
    }

    private function executeCreateImage(Trace $trace, array $input): bool
    {
        $client = $trace->getClient();
        
        if (!isset($input['image'])) {
            throw new \Exception("Image UUID not found in trace input");
        }

        $image = $this->entityManager->getRepository(Image::class)
            ->findOneBy(['uuid' => $input['image']]);

        if (!$image) {
            throw new \Exception("Image not found with UUID: {$input['image']}");
        }

        $partition = null;
        if (isset($input['diskNumber']) && isset($input['partitionNumber'])) {
            $partition = $this->entityManager->getRepository(Partition::class)
                ->findOneBy([
                    'client' => $client,
                    'diskNumber' => $input['diskNumber'],
                    'partitionNumber' => $input['partitionNumber']
                ]);
            
            if (!$partition) {
                throw new \Exception("Partition not found for client {$client->getUuid()} with disk {$input['diskNumber']} and partition {$input['partitionNumber']}");
            }
        }

        try {
            $response = $this->createImageAction->__invoke(
                queue: false,
                image: $image,
                partition: $partition,
                client: $client,
                gitRepositoryName: $input['gitRepositoryName'] ?? null,
                existingTrace: $trace
            );

            if ($response->getStatusCode() === 200) {
                $trace->setStatus(TraceStatus::SUCCESS);
                $trace->setFinishedAt(new \DateTime());
                $this->entityManager->persist($trace);
                $this->entityManager->flush();
                return true;
            }
            
            return false;
        } catch (\Exception $e) {
            
            return false;
        }
    }

    private function executeDeployImage(Trace $trace, array $input): bool
    {
        $client = $trace->getClient();
        
        if (!isset($input['imageImageRepository'])) {
            throw new \Exception("ImageImageRepository UUID not found in trace input");
        }

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

        if (!$imageImageRepository) {
            throw new \Exception("ImageImageRepository not found with UUID: {$input['imageImageRepository']}");
        }

        $deployInput = new DeployImageInput();
        $deployInput->method = $input['method'] ?? 'unicast';
        $deployInput->type = $input['type'] ?? 'monolithic';
        $deployInput->diskNumber = $input['diskNumber'] ?? 1;
        $deployInput->partitionNumber = $input['partitionNumber'] ?? 1;
        $deployInput->mcastMode = $input['mcastMode'] ?? 'duplex';
        $deployInput->mcastSpeed = $input['mcastSpeed'] ?? 100;
        $deployInput->mcastPort = $input['mcastPort'] ?? 8000;
        $deployInput->mcastIp = $input['mcastIp'] ?? '224.0.0.1';
        $deployInput->maxClients = $input['maxClients'] ?? 10;
        $deployInput->maxTime = $input['maxTime'] ?? 3600;
        $deployInput->p2pMode = $input['p2pMode'] ?? 'seed';
        $deployInput->p2pTime = $input['p2pTime'] ?? 300;

        $jobId = $this->deployImageAction->__invoke(
            imageImageRepository: $imageImageRepository,
            input: $deployInput,
            client: $client
        );

        $trace->setJobId($jobId);
        $this->entityManager->persist($trace);
        $this->entityManager->flush();

        return true;
    }

    private function executePartitionAssistant(Trace $trace, array $input): bool
    {
        $client = $trace->getClient();
        
        $partitionInput = new PartitionPostInput();
        $partitionInput->clients = [new ClientOutput($client)];
        
        $partitions = [];
        $diskNumber = 1;
        
        foreach ($input as $item) {
            if (isset($item['dis'])) {
                $diskNumber = (int)$item['dis'];
            } elseif (isset($item['par'])) {
                $partitionInputObj = new PartitionInput();
                $partitionInputObj->diskNumber = $diskNumber;
                $partitionInputObj->partitionNumber = (int)$item['par'];
                $partitionInputObj->partitionCode = $item['cpt'] ?? 'LINUX';
                $partitionInputObj->size = (float)($item['tam'] / 1024); 
                $partitionInputObj->filesystem = $item['sfi'] ?? 'EXT4';
                $partitionInputObj->format = ($item['ope'] ?? '0') === '1';
                $partitions[] = $partitionInputObj;
            }
        }
        
        $partitionInput->partitions = $partitions;
        $partitionInput->queue = false;

        try {
            $response = $this->partitionAssistantAction->__invoke($partitionInput, $trace);

            if ($response->getStatusCode() === 200) {
                $trace->setStatus(TraceStatus::SUCCESS);
                $trace->setFinishedAt(new \DateTime());
                $this->entityManager->persist($trace);
                $this->entityManager->flush();
                return true;
            }
            
            return false;
        } catch (\Exception $e) {
            
            return false;
        }
    }

    private function executeRunScript(Trace $trace, array $input): bool
    {
        $client = $trace->getClient();
        
        if (!isset($input['script'])) {
            throw new \Exception("Script not found in trace input");
        }

        $commandExecuteInput = new CommandExecuteInput();
        $commandExecuteInput->clients = [new ClientOutput($client)];
        $commandExecuteInput->script = $input['script'];
        $commandExecuteInput->queue = false;

        try {
            $response = $this->runScriptAction->__invoke($commandExecuteInput, $trace);
            
            if ($response->getStatusCode() === 200) {
                $trace->setStatus(TraceStatus::SUCCESS);
                $trace->setFinishedAt(new \DateTime());
                $this->entityManager->persist($trace);
                $this->entityManager->flush();
                return true;
            }
            
            return false;
        } catch (\Exception $e) {

            return false;
        }
    }
} 