<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Bridge\Symfony\Bundle\Controller\Admin;

use CpCreation\VitiCore\Bridge\Symfony\Bundle\Form\Admin\ClientImportUploadType;
use CpCreation\VitiCore\Product\Model\ProductPriceGroup;
use CpCreation\VitiCore\Product\Repository\ProductPriceGroupRepository;
use CpCreation\VitiCore\User\Import\ClientImportAnalysis;
use CpCreation\VitiCore\User\Import\ClientImportAnalyzer;
use CpCreation\VitiCore\User\Import\ClientImportException;
use CpCreation\VitiCore\User\Import\ClientImporter;
use CpCreation\VitiCore\User\Import\ImportedClientFileStorage;
use CpCreation\VitiCore\User\Model\ImportedClientGroup;
use CpCreation\VitiCore\User\Model\ImportedClientImport;
use CpCreation\VitiCore\User\Repository\ImportedClientGroupRepository;
use CpCreation\VitiCore\User\Repository\ImportedClientImportRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\Translation\TranslatorInterface;

#[Route('/imported-clients')]
#[IsGranted(new Expression("is_granted('ROLE_ADMIN')"))]
final class ImportedClientController extends AbstractController
{
    private const SESSION_KEY = 'viti_core_imported_client_file';
    private const IGNORE_GROUP_VALUE = '__ignore__';

    public function __construct(
        private readonly ImportedClientGroupRepository $importedClientGroupRepository,
        private readonly ImportedClientImportRepository $importedClientImportRepository,
        private readonly ProductPriceGroupRepository $productPriceGroupRepository,
        private readonly ClientImportAnalyzer $clientImportAnalyzer,
        private readonly ClientImporter $clientImporter,
        private readonly ImportedClientFileStorage $importedClientFileStorage,
        private readonly TranslatorInterface $translator,
    ) {}

    #[Route('', name: 'admin_imported_client')]
    public function index(Request $request): Response
    {
        $search = self::trimToNull($request->query->get('search'));

        return $this->render('@CpCreationVitiCore/admin/imported_client/index.html.twig', [
            'items' => $this->importedClientGroupRepository->findByEmailOrder($search),
            'search' => $search,
            'statistics' => $this->importedClientGroupRepository->getStatistics(),
            'group_distribution' => $this->importedClientGroupRepository->getGroupDistribution(),
            'recent_imports' => $this->importedClientImportRepository->findRecent(),
            'latest_import' => $this->importedClientImportRepository->findLatest(),
        ]);
    }

    #[Route('/{id}', name: 'admin_imported_client_show', requirements: ['id' => '[0-9a-fA-F-]{36}'])]
    public function show(ImportedClientGroup $importedClient): Response
    {
        return $this->render('@CpCreationVitiCore/admin/imported_client/show.html.twig', [
            'item' => $importedClient,
        ]);
    }

    #[Route('/{id}/toggle-application', name: 'admin_imported_client_toggle_application', requirements: ['id' => '[0-9a-fA-F-]{36}'], methods: ['POST'])]
    public function toggleApplication(ImportedClientGroup $importedClient, Request $request): Response
    {
        if (!$this->isCsrfTokenValid('toggle-imported-client-' . $importedClient->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', $this->translator->trans('admin_client_import.base.invalid_toggle_form'));

            return $this->redirectToRoute('admin_imported_client_show', ['id' => $importedClient->getId()]);
        }

        $importedClient->setApplyOnRegistration(!$importedClient->shouldApplyOnRegistration());
        $this->importedClientGroupRepository->save($importedClient);

        $message = $importedClient->shouldApplyOnRegistration()
            ? 'admin_client_import.base.application_enabled'
            : 'admin_client_import.base.application_disabled';
        $this->addFlash('alert-success', $this->translator->trans($message));

        return $this->redirectToRoute('admin_imported_client_show', ['id' => $importedClient->getId()]);
    }

    #[Route('/imports/{id}', name: 'admin_imported_client_import_show', requirements: ['id' => '[0-9a-fA-F-]{36}'])]
    public function showImport(ImportedClientImport $clientImport): Response
    {
        return $this->render('@CpCreationVitiCore/admin/imported_client/import_show.html.twig', [
            'item' => $clientImport,
        ]);
    }

    #[Route('/imports/{id}/download', name: 'admin_imported_client_import_download', requirements: ['id' => '[0-9a-fA-F-]{36}'])]
    public function downloadImport(ImportedClientImport $clientImport): BinaryFileResponse
    {
        $path = $this->importedClientFileStorage->path($clientImport->getStoredFilename());
        if (!is_file($path)) {
            throw $this->createNotFoundException();
        }

        return $this->file($path, $clientImport->getOriginalFilename(), ResponseHeaderBag::DISPOSITION_ATTACHMENT);
    }

    #[Route('/import', name: 'admin_imported_client_import', methods: ['GET', 'POST'])]
    public function import(Request $request): Response
    {
        if ($request->isMethod('GET')) {
            $this->cleanupStoredImport($request);
        }

        $form = $this->createForm(ClientImportUploadType::class);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $uploadedFile = $form->get('file')->getData();

            if (!$uploadedFile instanceof UploadedFile) {
                $form->get('file')->addError(new FormError($this->translator->trans('admin_client_import.error.file_required')));
            } else {
                $analysis = $this->analyzeUploadedFile($uploadedFile, $form->get('file'));

                if ($analysis instanceof ClientImportAnalysis) {
                    try {
                        $storedImport = $this->storeImportFile($uploadedFile, $request);
                    } catch (\Throwable) {
                        $form->get('file')->addError(new FormError($this->translator->trans('admin_client_import.error.storage_failed')));
                    }

                    if (isset($storedImport)) {
                        return $this->renderImportMapping($analysis, $storedImport['token']);
                    }
                }
            }
        }

        return $this->render('@CpCreationVitiCore/admin/imported_client/import.html.twig', [
            'upload_form' => $form->createView(),
            'analysis' => null,
        ]);
    }

    #[Route('/import/confirm', name: 'admin_imported_client_import_confirm', methods: ['POST'])]
    public function confirmImport(Request $request): Response
    {
        $token = self::trimToNull($request->request->get('import_token'));
        $storedImport = $request->getSession()->get(self::SESSION_KEY);

        if (!$this->isStoredImportValid($storedImport, $token)) {
            $this->cleanupStoredImport($request);
            $this->addFlash('alert-danger', $this->translator->trans('admin_client_import.error.expired'));

            return $this->redirectToRoute('admin_imported_client_import');
        }

        if (!$this->isCsrfTokenValid('client-import-' . $token, (string) $request->request->get('_token'))) {
            throw $this->createAccessDeniedException();
        }

        try {
            $analysis = $this->clientImportAnalyzer->analyze($storedImport['path']);
        } catch (ClientImportException $exception) {
            $this->cleanupStoredImport($request);
            $this->addFlash('alert-danger', $this->translator->trans($exception->getMessage()));

            return $this->redirectToRoute('admin_imported_client_import');
        }

        $availableGroups = $this->productPriceGroupRepository->findByPositionOrderAdmin();
        $groupsById = [];
        foreach ($availableGroups as $group) {
            $groupsById[$group->getId()->toString()] = $group;
        }

        $submittedMappings = $request->request->all('group_mapping');
        $selectedMappings = [];
        $groupMappings = [];
        $mappingErrors = [];

        foreach ($analysis->sourceGroups as $sourceGroup) {
            $mappingKey = $this->mappingKey($sourceGroup);
            $targetGroupId = self::trimToNull($submittedMappings[$mappingKey] ?? null);
            $selectedMappings[$mappingKey] = $targetGroupId;

            if (self::IGNORE_GROUP_VALUE === $targetGroupId) {
                $groupMappings[$sourceGroup] = null;

                continue;
            }

            if (null === $targetGroupId || !isset($groupsById[$targetGroupId])) {
                $mappingErrors[] = $this->translator->trans('admin_client_import.error.mapping_required', [
                    '%group%' => $sourceGroup,
                ]);

                continue;
            }

            $groupMappings[$sourceGroup] = $groupsById[$targetGroupId];
        }

        if ([] !== $mappingErrors) {
            return $this->renderImportMapping($analysis, $token, $mappingErrors, $selectedMappings);
        }

        $result = $this->clientImporter->import($analysis, $groupMappings);

        try {
            $archivedFile = $this->importedClientFileStorage->archive(
                $storedImport['path'],
                \is_string($storedImport['extension'] ?? null) ? $storedImport['extension'] : 'bin',
            );
            $importHistory = new ImportedClientImport(
                \is_string($storedImport['original_name'] ?? null) ? mb_substr($storedImport['original_name'], 0, 255) : 'import',
                $archivedFile->filename,
                \is_string($storedImport['content_type'] ?? null) ? mb_substr($storedImport['content_type'], 0, 127) : null,
                $archivedFile->size,
                $analysis->totalRows,
                $result->created,
                $result->updated,
                $result->unchanged,
                $result->ignored,
                $result->duplicatesInFile,
                $analysis->invalidEmailRows,
                $analysis->missingGroupRows,
                $this->buildGroupSummary($analysis, $groupMappings),
            );
            $this->importedClientImportRepository->save($importHistory);
        } catch (\Throwable) {
            if (isset($archivedFile)) {
                $this->importedClientFileStorage->remove($archivedFile->filename);
            }

            $this->cleanupStoredImport($request);
            $this->addFlash('alert-warning', $this->translator->trans('admin_client_import.history.storage_failed'));

            return $this->redirectToRoute('admin_imported_client');
        }

        $this->cleanupStoredImport($request);
        $this->addFlash('alert-success', $this->translator->trans('admin_client_import.result.summary', [
            '%created%' => $result->created,
            '%updated%' => $result->updated,
            '%unchanged%' => $result->unchanged,
            '%ignored%' => $result->ignored,
            '%duplicates%' => $result->duplicatesInFile,
            '%invalid%' => $analysis->invalidEmailRows,
            '%missing_group%' => $analysis->missingGroupRows,
        ]));

        return $this->redirectToRoute('admin_imported_client');
    }

    private function analyzeUploadedFile(UploadedFile $uploadedFile, \Symfony\Component\Form\FormInterface $fileForm): ?ClientImportAnalysis
    {
        try {
            return $this->clientImportAnalyzer->analyze($uploadedFile->getPathname());
        } catch (ClientImportException $exception) {
            $fileForm->addError(new FormError($this->translator->trans($exception->getMessage())));

            return null;
        }
    }

    /**
     * @return array{token: string, path: string}
     */
    private function storeImportFile(UploadedFile $uploadedFile, Request $request): array
    {
        $this->cleanupStoredImport($request);

        $token = bin2hex(random_bytes(24));
        $extension = mb_strtolower($uploadedFile->getClientOriginalExtension());
        $fileName = 'viti-core-client-import-' . $token . '.' . $extension;
        $uploadedFile->move(sys_get_temp_dir(), $fileName);
        $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $fileName;

        $request->getSession()->set(self::SESSION_KEY, [
            'token' => $token,
            'path' => $path,
            'expires_at' => time() + 3600,
            'original_name' => $uploadedFile->getClientOriginalName(),
            'content_type' => $uploadedFile->getClientMimeType(),
            'extension' => $extension,
        ]);

        return ['token' => $token, 'path' => $path];
    }

    private function cleanupStoredImport(Request $request): void
    {
        $storedImport = $request->getSession()->get(self::SESSION_KEY);
        $request->getSession()->remove(self::SESSION_KEY);

        if (!\is_array($storedImport) || !isset($storedImport['path']) || !\is_string($storedImport['path'])) {
            return;
        }

        $path = $storedImport['path'];
        if (sys_get_temp_dir() !== dirname($path) || !str_starts_with(basename($path), 'viti-core-client-import-')) {
            return;
        }

        if (is_file($path)) {
            @unlink($path);
        }
    }

    private function isStoredImportValid(mixed $storedImport, ?string $token): bool
    {
        return \is_array($storedImport)
            && \is_string($token)
            && isset($storedImport['token'], $storedImport['path'], $storedImport['expires_at'])
            && \is_string($storedImport['token'])
            && hash_equals($storedImport['token'], $token)
            && \is_string($storedImport['path'])
            && \is_int($storedImport['expires_at'])
            && $storedImport['expires_at'] >= time()
            && is_file($storedImport['path']);
    }

    /**
     * @param list<string>           $mappingErrors
     * @param array<string, ?string> $selectedMappings
     */
    private function renderImportMapping(
        ClientImportAnalysis $analysis,
        string $token,
        array $mappingErrors = [],
        array $selectedMappings = [],
    ): Response {
        $mappingRows = [];
        foreach ($analysis->sourceGroups as $sourceGroup) {
            $mappingRows[] = [
                'source' => $sourceGroup,
                'key' => $this->mappingKey($sourceGroup),
            ];
        }

        return $this->render('@CpCreationVitiCore/admin/imported_client/import.html.twig', [
            'upload_form' => null,
            'analysis' => $analysis,
            'import_token' => $token,
            'mapping_rows' => $mappingRows,
            'available_groups' => $this->productPriceGroupRepository->findByPositionOrderAdmin(),
            'mapping_errors' => $mappingErrors,
            'selected_mappings' => $selectedMappings,
            'ignore_group_value' => self::IGNORE_GROUP_VALUE,
        ]);
    }

    private function mappingKey(string $sourceGroup): string
    {
        return hash('sha256', $sourceGroup);
    }

    /**
     * @param array<string, ?ProductPriceGroup> $groupMappings
     *
     * @return list<array{source_group: string, target_group: ?string, rows: int, ignored: bool}>
     */
    private function buildGroupSummary(ClientImportAnalysis $analysis, array $groupMappings): array
    {
        $rowCounts = [];
        foreach ($analysis->rows as $row) {
            $rowCounts[$row->sourceGroup] = ($rowCounts[$row->sourceGroup] ?? 0) + 1;
        }

        $summary = [];
        foreach ($analysis->sourceGroups as $sourceGroup) {
            $targetGroup = $groupMappings[$sourceGroup] ?? null;
            $summary[] = [
                'source_group' => $sourceGroup,
                'target_group' => $targetGroup?->getName(),
                'rows' => $rowCounts[$sourceGroup] ?? 0,
                'ignored' => !$targetGroup instanceof ProductPriceGroup,
            ];
        }

        return $summary;
    }

    private static function trimToNull(mixed $value): ?string
    {
        if (!\is_string($value)) {
            return null;
        }

        $trimmed = trim($value);

        return '' === $trimmed ? null : $trimmed;
    }
}
