<?php

namespace App\Controller\OgRepository\Image;

use App\Controller\OgRepository\AbstractOgRepositoryController;
use App\Dto\Input\ImportImageRepositoryInput;
use App\Entity\Image;
use App\Entity\ImageImageRepository;
use App\Entity\ImageRepository;
use App\Model\CommandTypes;
use App\Model\ImageStatus;
use App\Model\TraceStatus;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
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\Component\HttpKernel\Exception\HttpException;

#[AsController]
class ImportAction extends AbstractOgRepositoryController
{
    /**
     * @throws TransportExceptionInterface
     * @throws ServerExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ClientExceptionInterface
     */
    public function __invoke(ImportImageRepositoryInput $input, ImageRepository $repository): JsonResponse
    {
        $this->validateRepositoryStatus($repository);
        
        $image = $input->name;
        $imageEntity = $this->getOrCreateImageEntity($image);
        $this->validateImageNotExistsInRepository($imageEntity, $repository);

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

        $content = $this->createTorrentSumRequest($repository, $image);
        $this->handleTorrentSumResponse($content);

        $imageImageRepositoryEntity = $this->createImageImageRepositoryEntity($imageEntity, $repository, $image);
        $this->entityManager->persist($imageImageRepositoryEntity);
        $this->entityManager->flush();

        $this->createService->__invoke(
            null, 
            CommandTypes::CREATE_IMAGE_AUX_FILE, 
            TraceStatus::IN_PROGRESS, 
            $content['job_id'], 
            [
                'imageName' => $image,
                'imageImageRepositoryUuid' => $imageImageRepositoryEntity->getUuid(),
            ]
        );

        return new JsonResponse(data: [], status: Response::HTTP_OK);
    }

    private function validateRepositoryStatus(ImageRepository $repository): void
    {
        $content = $this->createRequest('GET', 'http://'.$repository->getIp(). ':8006/ogrepository/v1/status');

        if (isset($content['error']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) {
            throw new BadRequestHttpException('An error occurred while fetching the status: ' . $content['details']);
        }
    }

    private function getOrCreateImageEntity(string $imageName): Image
    {
        $imageEntity = $this->entityManager->getRepository(Image::class)->findOneBy(['name' => $imageName]);

        if (!$imageEntity) {
            $imageEntity = new Image();
            $imageEntity->setName($imageName);
            $imageEntity->setType('monolithic');
            $imageEntity->setRemotePc(false);
            $imageEntity->setIsGlobal(false);

            $this->entityManager->persist($imageEntity);
        }

        return $imageEntity;
    }

    private function validateImageNotExistsInRepository(Image $imageEntity, ImageRepository $repository): void
    {
        $imageImageRepositoryEntity = $this->entityManager->getRepository(ImageImageRepository::class)
            ->findOneBy(['image' => $imageEntity, 'repository' => $repository]);

        if ($imageImageRepositoryEntity) {
            throw new BadRequestHttpException('This image already exists in this repository');
        }
    }

    private function createTorrentSumRequest(ImageRepository $repository, string $image): array
    {
        $params = [
            'json' => [
                'image' => $image.'.img'
            ]
        ];

        return $this->createRequest('POST', 'http://'.$repository->getIp().':8006/ogrepository/v1/images/torrentsum', $params);
    }

    private function handleTorrentSumResponse(array $content): void
    {
        if (!isset($content['error'])) {
            return;
        }

        $errorMessage = $this->extractErrorMessage($content);
        $errorCode = $content['code'] ?? Response::HTTP_BAD_REQUEST;

        $this->throwAppropriateException($errorCode, $errorMessage);
    }

    private function extractErrorMessage(array $content): string
    {
        $errorMessage = $content['error'];
        $errorDetails = $content['details'] ?? null;

        if (!$errorDetails) {
            return $errorMessage;
        }

        if (is_array($errorDetails)) {
            return $errorDetails['message'] ?? $errorDetails['error'] ?? $errorMessage;
        }

        if (is_string($errorDetails) && !empty($errorDetails)) {
            return $errorDetails;
        }

        return $errorMessage;
    }

    private function throwAppropriateException(int $errorCode, string $errorMessage): void
    {
        if ($errorCode >= 400 && $errorCode < 500) {
            throw new BadRequestHttpException($errorMessage);
        }

        if ($errorCode >= 500) {
            throw new HttpException($errorCode, $errorMessage);
        }

        throw new BadRequestHttpException($errorMessage);
    }

    private function createImageImageRepositoryEntity(Image $imageEntity, ImageRepository $repository, string $imageName): ImageImageRepository
    {
        $imageImageRepositoryEntity = new ImageImageRepository();
        $imageImageRepositoryEntity->setName($imageEntity->getName());
        $imageImageRepositoryEntity->setStatus(ImageStatus::AUX_FILES_PENDING);
        $imageImageRepositoryEntity->setImage($imageEntity);
        $imageImageRepositoryEntity->setRepository($repository);
        $imageImageRepositoryEntity->setVersion($this->extractVersionFromImageName($imageName));

        return $imageImageRepositoryEntity;
    }

    private function extractVersionFromImageName(string $imageName): int
    {
        $patterns = ['/_v(\d+)$/', '/-v(\d+)$/', '/v(\d+)$/'];
        
        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $imageName, $matches)) {
                return (int) $matches[1];
            }
        }
        
        return 1;
    }
}