<?php

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Cart\LegacyOrderImport;

use Symfony\Component\Console\Style\SymfonyStyle;

final readonly class LegacyOrderImportMappingWizard
{
    public const int PAGE_SIZE = 20;

    public function __construct(
        private LegacyOrderImportMappingGenerator $mappingFiles,
    ) {
    }

    /**
     * @param array<string, mixed> $draft
     *
     * @return array{updated: int, remaining: int, quit: bool}
     */
    public function run(
        array $draft,
        string $path,
        SymfonyStyle $io,
        bool $includeResolved = false,
    ): array {
        if (1 !== ($draft['schema_version'] ?? null)) {
            throw new \RuntimeException('L assistant interactif exige un fichier genere avec schema_version 1.');
        }

        $tasks = $this->tasks($draft, $includeResolved);
        if ([] === $tasks) {
            $io->success('Aucune correspondance ne reste a completer.');

            return ['updated' => 0, 'remaining' => 0, 'quit' => false];
        }

        $position = 0;
        $updated = 0;
        $quit = false;
        $pages = [];

        while (isset($tasks[$position])) {
            $task = $tasks[$position];
            $taskKey = $task['section'].':'.$task['source'];
            $pageCount = max(1, (int) ceil(\count($task['candidates']) / self::PAGE_SIZE));
            $page = min($pages[$taskKey] ?? 0, $pageCount - 1);
            $pages[$taskKey] = $page;
            $pageCandidates = \array_slice($task['candidates'], $page * self::PAGE_SIZE, self::PAGE_SIZE);

            $this->renderTask($io, $task, $position, \count($tasks), $page, $pageCount, $pageCandidates);
            $answer = mb_strtolower(mb_trim((string) ($io->ask(
                'Votre choix (numero, n/p page, r retour, s suivante, d detacher, q quitter)',
                's',
            ) ?? 's')));

            if ('q' === $answer) {
                $quit = true;

                break;
            }

            if ('r' === $answer) {
                if (0 === $position) {
                    $io->warning('Vous etes deja sur la premiere correspondance.');
                } else {
                    --$position;
                }

                continue;
            }

            if ('s' === $answer || '' === $answer) {
                ++$position;

                continue;
            }

            if ('n' === $answer) {
                if ($page + 1 >= $pageCount) {
                    $io->warning('Vous etes deja sur la derniere page.');
                } else {
                    $pages[$taskKey] = $page + 1;
                }

                continue;
            }

            if ('p' === $answer) {
                if (0 === $page) {
                    $io->warning('Vous etes deja sur la premiere page.');
                } else {
                    $pages[$taskKey] = $page - 1;
                }

                continue;
            }

            if ('d' === $answer) {
                $draft[$task['section']][$task['source']] = null;
                $this->mappingFiles->replace($path, $draft);
                $tasks[$position]['current'] = null;
                ++$updated;
                ++$position;

                continue;
            }

            if (ctype_digit($answer)) {
                $candidateIndex = ((int) $answer) - 1;
                if (!isset($pageCandidates[$candidateIndex])) {
                    $io->error('Ce numero n existe pas sur la page affichee.');

                    continue;
                }

                $draft[$task['section']][$task['source']] = $pageCandidates[$candidateIndex]['value'];
                $this->mappingFiles->replace($path, $draft);
                $tasks[$position]['current'] = $pageCandidates[$candidateIndex]['value'];
                ++$updated;
                ++$position;

                continue;
            }

            $io->error('Choix inconnu. Utilisez un numero affiche ou une commande n, p, r, s, d, q.');
        }

        $remaining = $this->countUnresolvedMappings($draft);
        if ($quit) {
            $io->note('Les choix deja effectues ont ete enregistres dans le fichier JSON.');
        } elseif (0 === $remaining) {
            $io->success('Toutes les correspondances sont completees dans le fichier JSON.');
        } else {
            $io->warning(\sprintf('%d correspondance(s) restent a completer dans le fichier JSON.', $remaining));
        }

        return ['updated' => $updated, 'remaining' => $remaining, 'quit' => $quit];
    }

    /**
     * @param array{
     *     type: string,
     *     section: string,
     *     source: string,
     *     source_label: string,
     *     source_detail: string,
     *     source_price: ?string,
     *     current: string|null,
     *     candidates: list<array{value: string, label: string, detail: string, price: ?string}>
     * } $task
     * @param list<array{value: string, label: string, detail: string, price: ?string}> $pageCandidates
     */
    private function renderTask(
        SymfonyStyle $io,
        array $task,
        int $position,
        int $taskCount,
        int $page,
        int $pageCount,
        array $pageCandidates,
    ): void {
        $io->section(\sprintf('Correspondance %d/%d - %s', $position + 1, $taskCount, $task['type']));
        $definitions = [
            ['Source' => $task['source_label']],
            ['Identifiant source' => $task['source']],
            ['Utilisation' => $task['source_detail']],
        ];
        if (null !== $task['source_price']) {
            $definitions[] = ['Prix historique' => $task['source_price']];
        }
        $definitions[] = ['Valeur actuelle' => $this->currentLabel($task['current'], $task['candidates'])];
        $definitions[] = ['Page' => \sprintf('%d/%d (%d choix maximum par page)', $page + 1, $pageCount, self::PAGE_SIZE)];
        $io->definitionList(...$definitions);

        if ([] === $pageCandidates) {
            $io->warning('Aucune cible n est disponible. Utilisez d pour detacher ou s pour passer.');

            return;
        }

        $isVariant = 'variants' === $task['section'];
        $rows = [];
        foreach ($pageCandidates as $index => $candidate) {
            $row = [
                (string) ($index + 1),
                $candidate['label'],
            ];
            if ($isVariant) {
                $row[] = $candidate['price'] ?? 'Non renseigne';
            }
            $row[] = $candidate['detail'];
            $rows[] = $row;
        }

        $headers = ['Numero', 'Cible'];
        if ($isVariant) {
            $headers[] = 'Prix actuel';
        }
        $headers[] = 'Identifiant / detail';
        $io->table($headers, $rows);
    }

    /**
     * @param list<array{value: string, label: string, detail: string, price: ?string}> $candidates
     */
    private function currentLabel(?string $current, array $candidates): string
    {
        if (LegacyOrderImportMapping::UNRESOLVED === $current) {
            return 'A completer';
        }

        if (null === $current) {
            return 'Detachement historique explicite';
        }

        foreach ($candidates as $candidate) {
            if ($candidate['value'] === $current) {
                return $candidate['label'].' ('.$candidate['value'].')';
            }
        }

        return $current;
    }

    /**
     * @param array<string, mixed> $draft
     *
     * @return list<array{
     *     type: string,
     *     section: string,
     *     source: string,
     *     source_label: string,
     *     source_detail: string,
     *     source_price: ?string,
     *     current: string|null,
     *     candidates: list<array{value: string, label: string, detail: string, price: ?string}>
     * }>
     */
    private function tasks(array $draft, bool $includeResolved): array
    {
        $definitions = [
            [
                'type' => 'Variante produit',
                'section' => 'variants',
                'source_context' => '_source_variants',
                'candidates' => $this->variantCandidates($draft['_target_variants'] ?? null),
            ],
            [
                'type' => 'Moyen de paiement',
                'section' => 'payment_methods',
                'source_context' => '_source_payment_methods',
                'candidates' => $this->paymentCandidates($draft['_target_payment_methods'] ?? null),
            ],
            [
                'type' => 'Mode de livraison',
                'section' => 'delivery_methods',
                'source_context' => '_source_delivery_methods',
                'candidates' => $this->deliveryCandidates($draft['_target_delivery_methods'] ?? null),
            ],
        ];
        $tasks = [];

        foreach ($definitions as $definition) {
            $mappings = $draft[$definition['section']] ?? null;
            if (!\is_array($mappings)) {
                throw new \RuntimeException(\sprintf('La section "%s" du fichier genere est invalide.', $definition['section']));
            }

            $sourceContexts = $draft[$definition['source_context']] ?? [];
            $sourceContexts = \is_array($sourceContexts) ? $sourceContexts : [];

            foreach ($mappings as $source => $current) {
                if (!\is_string($source) || (!\is_string($current) && null !== $current)) {
                    throw new \RuntimeException(\sprintf('Une entree de la section "%s" est invalide.', $definition['section']));
                }

                if (!$includeResolved && LegacyOrderImportMapping::UNRESOLVED !== $current) {
                    continue;
                }

                $context = $sourceContexts[$source] ?? [];
                $context = \is_array($context) ? $context : [];
                $tasks[] = [
                    'type' => $definition['type'],
                    'section' => $definition['section'],
                    'source' => $source,
                    'source_label' => $this->sourceLabel($definition['section'], $source, $context),
                    'source_detail' => $this->sourceDetail($definition['section'], $context),
                    'source_price' => 'variants' === $definition['section']
                        ? $this->historicalPriceLabel($context['prices'] ?? null)
                        : null,
                    'current' => $current,
                    'candidates' => $definition['candidates'],
                ];
            }
        }

        return $tasks;
    }

    /**
     * @return list<array{value: string, label: string, detail: string, price: ?string}>
     */
    private function variantCandidates(mixed $value): array
    {
        if (!\is_array($value)) {
            return [];
        }

        $candidates = [];
        foreach ($value as $id => $variant) {
            if (!\is_string($id) || !\is_array($variant)) {
                continue;
            }

            $label = $this->string($variant['label'] ?? null) ?? $id;
            $sku = $this->string($variant['sku'] ?? null);
            $candidates[] = [
                'value' => $id,
                'label' => $label,
                'detail' => null === $sku ? $id : 'SKU '.$sku.' - '.$id,
                'price' => $this->targetPriceLabel($variant),
            ];
        }

        return $this->sortCandidates($candidates);
    }

    /**
     * @return list<array{value: string, label: string, detail: string, price: ?string}>
     */
    private function paymentCandidates(mixed $value): array
    {
        if (!\is_array($value)) {
            return [];
        }

        $candidates = [];
        foreach ($value as $method) {
            if (!\is_array($method)) {
                continue;
            }

            $id = $this->string($method['id'] ?? null);
            if (null === $id) {
                continue;
            }

            $candidates[] = [
                'value' => $id,
                'label' => $this->string($method['method'] ?? null) ?? $id,
                'detail' => $id,
                'price' => null,
            ];
        }

        return $this->sortCandidates($candidates);
    }

    /**
     * @return list<array{value: string, label: string, detail: string, price: ?string}>
     */
    private function deliveryCandidates(mixed $value): array
    {
        if (!\is_array($value)) {
            return [];
        }

        $candidates = [];
        foreach ($value as $id => $method) {
            if (!\is_string($id) || !\is_array($method)) {
                continue;
            }

            $candidates[] = [
                'value' => $id,
                'label' => $this->string($method['label'] ?? null) ?? $id,
                'detail' => $id,
                'price' => null,
            ];
        }

        return $this->sortCandidates($candidates);
    }

    /**
     * @param list<array{value: string, label: string, detail: string, price: ?string}> $candidates
     *
     * @return list<array{value: string, label: string, detail: string, price: ?string}>
     */
    private function sortCandidates(array $candidates): array
    {
        usort($candidates, static function (array $left, array $right): int {
            $labelComparison = strnatcasecmp($left['label'], $right['label']);

            return 0 === $labelComparison ? strcmp($left['value'], $right['value']) : $labelComparison;
        });

        return $candidates;
    }

    /**
     * @param array<string, mixed> $context
     */
    private function sourceLabel(string $section, string $source, array $context): string
    {
        return match ($section) {
            'variants', 'delivery_methods' => $this->string($context['label'] ?? null) ?? $source,
            default => $source,
        };
    }

    /**
     * @param array<string, mixed> $context
     */
    private function sourceDetail(string $section, array $context): string
    {
        return match ($section) {
            'variants' => \sprintf(
                '%d commande(s), %d ligne(s), quantite %d',
                (int) ($context['orders'] ?? 0),
                (int) ($context['lines'] ?? 0),
                (int) ($context['quantity'] ?? 0),
            ),
            'payment_methods' => \sprintf(
                '%d commande(s) payee(s), %d non payee(s)',
                (int) ($context['paid_orders'] ?? 0),
                (int) ($context['unpaid_orders'] ?? 0),
            ),
            'delivery_methods' => \sprintf('%d commande(s)', (int) ($context['orders'] ?? 0)),
            default => '',
        };
    }

    /**
     * @param array<string, mixed> $draft
     */
    private function countUnresolvedMappings(array $draft): int
    {
        $count = 0;

        foreach (['variants', 'payment_methods', 'delivery_methods'] as $section) {
            $values = $draft[$section] ?? [];
            if (!\is_array($values)) {
                continue;
            }

            foreach ($values as $value) {
                if (LegacyOrderImportMapping::UNRESOLVED === $value) {
                    ++$count;
                }
            }
        }

        return $count;
    }

    private function historicalPriceLabel(mixed $value): string
    {
        if (!\is_array($value)) {
            return 'Non renseigne';
        }

        $prices = [];
        foreach ($value as $price) {
            $price = $this->money($price);
            if (null !== $price) {
                $prices[$price] = $price;
            }
        }

        sort($prices, \SORT_NUMERIC);

        return [] === $prices
            ? 'Non renseigne'
            : implode(' / ', array_map($this->formatMoney(...), $prices));
    }

    /**
     * @param array<string, mixed> $variant
     */
    private function targetPriceLabel(array $variant): string
    {
        $price = $this->money($variant['price'] ?? null);
        if (null === $price) {
            return 'Non renseigne';
        }

        $discount = $this->money($variant['price_discount'] ?? null);
        if (null !== $discount && $discount > 0 && $discount < $price) {
            return \sprintf('%s (au lieu de %s)', $this->formatMoney($discount), $this->formatMoney($price));
        }

        return $this->formatMoney($price);
    }

    private function formatMoney(int $price): string
    {
        return number_format($price / 100, 2, ',', ' ').' EUR';
    }

    private function money(mixed $value): ?int
    {
        if (!\is_int($value) || $value < 0) {
            return null;
        }

        return $value;
    }

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

        $value = mb_trim($value);

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