<?php
declare(strict_types=1);

namespace Autogedal\FrontendI18n\Console\Command;

use Autogedal\FrontendI18n\Model\AreaPathsResolver;
use Autogedal\FrontendI18n\Model\PhraseCollector;
use Autogedal\FrontendI18n\Model\TranslationCsvReader;
use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class CompareModuleTranslationsCommand extends Command
{
    private const COMMAND_NAME = 'autogedal:i18n:compare';
    private const OPTION_AREA = 'area';
    private const OPTION_TARGET_CSV = 'target-csv';
    private const OPTION_MODULES_GLOB = 'modules-glob';
    private const OPTION_EXCLUDE_MODULE_PREFIX = 'exclude-module-prefix';
    private const OPTION_DIFFERENT_OUTPUT = 'different-output';
    private const OPTION_MISSING_OUTPUT = 'missing-output';

    public function __construct(
        private readonly AreaPathsResolver $areaPathsResolver,
        private readonly PhraseCollector $phraseCollector,
        private readonly TranslationCsvReader $translationCsvReader,
        ?string $name = null
    ) {
        parent::__construct($name);
    }

    protected function configure(): void
    {
        $this->setName(self::COMMAND_NAME)
            ->setDescription('Compare all app/code ro_RO.csv translations against the generated Portochild ro_RO.csv')
            ->setDefinition([
                new InputOption(
                    self::OPTION_AREA,
                    null,
                    InputOption::VALUE_REQUIRED,
                    'Area to filter by: frontend or backend',
                    AreaPathsResolver::AREA_FRONTEND
                ),
                new InputOption(
                    self::OPTION_TARGET_CSV,
                    null,
                    InputOption::VALUE_REQUIRED,
                    'Target translation CSV to compare against',
                    BP . '/app/design/frontend/Smartwave/portochild/i18n/ro_RO.csv'
                ),
                new InputOption(
                    self::OPTION_MODULES_GLOB,
                    null,
                    InputOption::VALUE_REQUIRED,
                    'Glob for module CSV files to compare',
                    BP . '/app/code/*/*/i18n/ro_RO.csv'
                ),
                new InputOption(
                    self::OPTION_EXCLUDE_MODULE_PREFIX,
                    null,
                    InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
                    'Module namespace prefixes to exclude from the comparison'
                ),
                new InputOption(
                    self::OPTION_DIFFERENT_OUTPUT,
                    null,
                    InputOption::VALUE_REQUIRED,
                    'CSV report for rows whose translations differ',
                    BP . '/var/i18n/ro_RO.module-vs-portochild.different.csv'
                ),
                new InputOption(
                    self::OPTION_MISSING_OUTPUT,
                    null,
                    InputOption::VALUE_REQUIRED,
                    'CSV report for rows missing from the target CSV',
                    BP . '/var/i18n/ro_RO.module-vs-portochild.missing.csv'
                ),
            ]);
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        try {
            $area = strtolower(trim((string)$input->getOption(self::OPTION_AREA)));
            $this->areaPathsResolver->assertValidArea($area);

            $targetCsv = trim((string)$input->getOption(self::OPTION_TARGET_CSV));
            $modulesGlob = trim((string)$input->getOption(self::OPTION_MODULES_GLOB));
            $excludedPrefixes = array_values(array_filter(array_map(
                static fn ($value): string => trim((string)$value),
                $input->getOption(self::OPTION_EXCLUDE_MODULE_PREFIX) ?: []
            )));
            $differentOutput = trim((string)$input->getOption(self::OPTION_DIFFERENT_OUTPUT));
            $missingOutput = trim((string)$input->getOption(self::OPTION_MISSING_OUTPUT));

            if ($targetCsv === '') {
                throw new \InvalidArgumentException('The --target-csv option is required.');
            }

            if ($modulesGlob === '') {
                throw new \InvalidArgumentException('The --modules-glob option is required.');
            }

            if ($differentOutput === '') {
                throw new \InvalidArgumentException('The --different-output option is required.');
            }

            if ($missingOutput === '') {
                throw new \InvalidArgumentException('The --missing-output option is required.');
            }

            $targetTranslations = $this->translationCsvReader->read($targetCsv);
            $moduleCsvPaths = $this->collectModuleCsvPaths($modulesGlob, $excludedPrefixes);
            if ($moduleCsvPaths === []) {
                throw new \RuntimeException('No module CSV files matched: ' . $modulesGlob);
            }

            $allowedSources = array_fill_keys($this->phraseCollector->collect($area), true);
            [$differentRows, $missingRows] = $this->collectDifferences($moduleCsvPaths, $targetTranslations, $allowedSources);
            $this->writeReport($differentOutput, $differentRows);
            $this->writeReport($missingOutput, $missingRows);

            $output->writeln('<info>Compared ' . count($moduleCsvPaths) . ' module CSV file(s).</info>');
            $output->writeln('<info>Area filter: ' . $area . '</info>');
            $output->writeln('<info>Frontend source phrases collected: ' . count($allowedSources) . '</info>');
            if ($excludedPrefixes !== []) {
                $output->writeln('<info>Excluded module prefixes: ' . implode(', ', $excludedPrefixes) . '</info>');
            }
            $output->writeln('<info>Found ' . count($differentRows) . ' differing row(s).</info>');
            $output->writeln('<info>Found ' . count($missingRows) . ' missing row(s).</info>');
            $output->writeln('<info>Different report written to: ' . $differentOutput . '</info>');
            $output->writeln('<info>Missing report written to: ' . $missingOutput . '</info>');

            return Cli::RETURN_SUCCESS;
        } catch (\InvalidArgumentException $e) {
            $output->writeln('<error>' . $e->getMessage() . '</error>');
            return Cli::RETURN_FAILURE;
        } catch (\Throwable $e) {
            $output->writeln('<error>' . $e->getMessage() . '</error>');
            return Cli::RETURN_FAILURE;
        }
    }

    /**
     * @param array<string,string> $targetTranslations
     * @param array<string,true> $allowedSources
     * @return array{0:array<int,array{source:string,target_translation:string,module_translation:string,module_file:string,module_line:int,status:string}>,1:array<int,array{source:string,target_translation:string,module_translation:string,module_file:string,module_line:int,status:string}>}
     */
    private function collectDifferences(array $moduleCsvPaths, array $targetTranslations, array $allowedSources): array
    {
        $differentRows = [];
        $missingRows = [];
        $seen = [];

        foreach ($moduleCsvPaths as $moduleCsvPath) {
            foreach ($this->scanCsvRows($moduleCsvPath) as $row) {
                $source = $row['raw_source'];
                $moduleTranslation = $row['raw_translation'];

                if (trim($source) === '') {
                    continue;
                }

                if (!isset($allowedSources[$source])) {
                    continue;
                }

                $targetExists = array_key_exists($source, $targetTranslations);
                $targetTranslation = $targetExists ? (string)$targetTranslations[$source] : '';
                if ($targetExists && $targetTranslation === $moduleTranslation) {
                    continue;
                }

                $status = $targetExists ? 'different' : 'missing-in-target';
                $key = $source . "\n" . $moduleTranslation . "\n" . $moduleCsvPath . "\n" . $status;
                if (isset($seen[$key])) {
                    continue;
                }

                $seen[$key] = true;
                $row = [
                    'source' => $source,
                    'target_translation' => $targetTranslation,
                    'module_translation' => $moduleTranslation,
                    'module_file' => $moduleCsvPath,
                    'module_line' => $row['line'],
                    'status' => $status,
                ];

                if ($status === 'different') {
                    $differentRows[] = $row;
                    continue;
                }

                $missingRows[] = $row;
            }
        }

        return [$differentRows, $missingRows];
    }

    /**
     * @return array<int,array{source:string,translation:string,raw_source:string,raw_translation:string,line:int}>
     */
    private function scanCsvRows(string $path): array
    {
        if (!is_file($path)) {
            throw new \InvalidArgumentException('Module CSV not found: ' . $path);
        }

        $handle = fopen($path, 'rb');
        if ($handle === false) {
            throw new \RuntimeException('Unable to open module CSV: ' . $path);
        }

        $rows = [];
        $line = 0;

        while (($row = fgetcsv($handle)) !== false) {
            $line++;
            if (count($row) < 2) {
                continue;
            }

            $source = trim((string)$row[0]);
            $translation = trim((string)$row[1]);
            if ($source === '') {
                continue;
            }

            $rows[] = [
                'source' => (string)$row[0],
                'translation' => (string)$row[1],
                'raw_source' => (string)$row[0],
                'raw_translation' => (string)$row[1],
                'line' => $line,
            ];
        }

        fclose($handle);

        return $rows;
    }

    /**
     * @return string[]
     */
    private function collectModuleCsvPaths(string $modulesGlob, array $excludedPrefixes = []): array
    {
        $paths = glob($modulesGlob) ?: [];
        sort($paths);

        $excludedLookup = array_fill_keys($excludedPrefixes, true);

        return array_values(array_filter($paths, static function (string $path) use ($excludedLookup): bool {
            if (!is_file($path)) {
                return false;
            }

            if ($excludedLookup === []) {
                return true;
            }

            $normalizedPath = str_replace('\\', '/', $path);
            if (preg_match('~/app/code/([^/]+)/([^/]+)/~', $normalizedPath, $matches) !== 1) {
                return true;
            }

            $vendor = $matches[1];
            $module = $matches[2];
            $combined = $vendor . '_' . $module;

            return !isset($excludedLookup[$vendor])
                && !isset($excludedLookup[$module])
                && !isset($excludedLookup[$combined]);
        }));
    }

    /**
     * @param array<int,array{source:string,target_translation:string,module_translation:string,module_file:string,module_line:int,status:string}> $rows
     */
    private function writeReport(string $path, array $rows): void
    {
        $directory = dirname($path);
        if (!is_dir($directory) && !mkdir($directory, 0775, true) && !is_dir($directory)) {
            throw new \RuntimeException('Unable to create directory: ' . $directory);
        }

        $handle = fopen($path, 'wb');
        if ($handle === false) {
            throw new \RuntimeException('Unable to open report file: ' . $path);
        }

        fputcsv($handle, ['source', 'target_translation', 'module_translation', 'module_file', 'module_line', 'status']);

        foreach ($rows as $row) {
            fputcsv($handle, [
                $row['source'],
                $row['target_translation'],
                $row['module_translation'],
                $row['module_file'],
                (string)$row['module_line'],
                $row['status'],
            ]);
        }

        fclose($handle);
    }
}
