<?php

namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Yaml\Yaml;
use Symfony\Contracts\HttpClient\HttpClientInterface;

#[AsCommand(
    name: 'app:translate',
    description: 'Translate YAML translation files from French to other languages using LibreTranslate',
)]
class TranslateCommand extends Command
{
    private const TARGET_LANGUAGES = ['en', 'es', 'de', 'it'];

    public function __construct(
        private HttpClientInterface $httpClient,
        #[Autowire('%kernel.project_dir%')] private string $projectDir,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this->addOption('url', null, InputOption::VALUE_OPTIONAL, 'LibreTranslate API URL', 'http://libretranslate:5000');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $apiUrl = $input->getOption('url');
        $translationsDir = $this->projectDir.'/translations';

        if (!$this->waitForApi($apiUrl, $io)) {
            $io->error('LibreTranslate is not reachable at '.$apiUrl);

            return Command::FAILURE;
        }

        $frFiles = glob($translationsDir.'/*.fr.yaml');
        if (!$frFiles) {
            $io->warning('No French translation files found in '.$translationsDir);

            return Command::SUCCESS;
        }

        $totalKeys = 0;
        $totalFiles = 0;

        foreach ($frFiles as $frFile) {
            [$keys, $files] = $this->processDomain($frFile, $translationsDir, $apiUrl, $io);
            $totalKeys += $keys;
            $totalFiles += $files;
        }

        $io->success(sprintf('Done: %d keys translated across %d files.', $totalKeys, $totalFiles));

        return Command::SUCCESS;
    }

    /**
     * @return array{int, int}
     */
    private function processDomain(string $frFile, string $translationsDir, string $apiUrl, SymfonyStyle $io): array
    {
        $basename = basename($frFile);
        $domain = str_replace('.fr.yaml', '', $basename);

        $io->section('Translating: '.$basename);

        $frData = Yaml::parseFile($frFile);
        if (!\is_array($frData) || 0 === \count($frData)) {
            $io->text('  Skipped (empty)');

            return [0, 0];
        }

        $totalKeys = 0;
        $totalFiles = 0;

        foreach (self::TARGET_LANGUAGES as $lang) {
            $targetFile = $translationsDir.'/'.$domain.'.'.$lang.'.yaml';
            $result = $this->processLanguage($frData, $targetFile, $lang, $apiUrl, $io);
            $totalKeys += $result[0];
            $totalFiles += $result[1];
        }

        return [$totalKeys, $totalFiles];
    }

    /**
     * @param array<string, string> $frData
     *
     * @return array{int, int}
     */
    private function processLanguage(array $frData, string $targetFile, string $lang, string $apiUrl, SymfonyStyle $io): array
    {
        $existing = $this->loadExisting($targetFile);
        $existingCount = \count($existing);
        $toTranslate = $this->findMissingKeys($frData, $existing);
        $existing = $this->removeObsoleteKeys($existing, $frData);
        $removedCount = $existingCount - \count($existing);

        if (0 === \count($toTranslate)) {
            return $this->handleUpToDate($frData, $existing, $removedCount, $targetFile, $lang, $io);
        }

        $translated = $this->translateBatch($apiUrl, $toTranslate, $lang, $io);
        $ordered = $this->mergeAndOrder($frData, array_merge($existing, $translated));

        file_put_contents($targetFile, Yaml::dump($ordered, 4, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK));

        $io->text(sprintf('  [%s] Translated %d new keys (%d total)', $lang, \count($toTranslate), \count($ordered)));

        return [\count($toTranslate), 1];
    }

    /**
     * @return array<string, string>
     */
    private function loadExisting(string $targetFile): array
    {
        if (!file_exists($targetFile)) {
            return [];
        }

        return Yaml::parseFile($targetFile) ?? [];
    }

    /**
     * @param array<string, string> $frData
     * @param array<string, string> $existing
     *
     * @return array<string, string>
     */
    private function findMissingKeys(array $frData, array $existing): array
    {
        $toTranslate = [];
        foreach ($frData as $key => $value) {
            if (!isset($existing[$key]) || '' === $existing[$key]) {
                $toTranslate[$key] = $value;
            }
        }

        return $toTranslate;
    }

    /**
     * @param array<string, string> $existing
     * @param array<string, string> $frData
     *
     * @return array<string, string>
     */
    private function removeObsoleteKeys(array $existing, array $frData): array
    {
        foreach (array_keys($existing) as $key) {
            if (!\array_key_exists($key, $frData)) {
                unset($existing[$key]);
            }
        }

        return $existing;
    }

    /**
     * @param array<string, string> $frData
     * @param array<string, string> $existing
     *
     * @return array{int, int}
     */
    private function handleUpToDate(array $frData, array $existing, int $removedCount, string $targetFile, string $lang, SymfonyStyle $io): array
    {
        $io->text(sprintf('  [%s] Already up to date (%d keys)', $lang, \count($frData)));

        if ($removedCount > 0) {
            file_put_contents($targetFile, Yaml::dump($existing, 4, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK));
            $io->text(sprintf('  [%s] Removed %d obsolete keys', $lang, $removedCount));
        }

        return [0, 0];
    }

    /**
     * @param array<string, string> $frData
     * @param array<string, string> $merged
     *
     * @return array<string, string>
     */
    private function mergeAndOrder(array $frData, array $merged): array
    {
        $ordered = [];
        foreach ($frData as $key => $value) {
            if (isset($merged[$key])) {
                $ordered[$key] = $merged[$key];
            }
        }

        return $ordered;
    }

    /**
     * @param array<string, string> $texts
     *
     * @return array<string, string>
     */
    private function translateBatch(string $apiUrl, array $texts, string $targetLang, SymfonyStyle $io): array
    {
        $result = [];

        foreach ($texts as $key => $text) {
            $translated = $this->translateText($apiUrl, (string) $text, $targetLang);
            if (null !== $translated) {
                $result[$key] = $this->preservePlaceholders((string) $text, $translated);
            } else {
                $io->warning(sprintf('  Failed to translate key "%s" to %s', $key, $targetLang));
                $result[$key] = (string) $text;
            }
        }

        return $result;
    }

    private function translateText(string $apiUrl, string $text, string $targetLang): ?string
    {
        try {
            $response = $this->httpClient->request('POST', $apiUrl.'/translate', [
                'json' => [
                    'q' => $text,
                    'source' => 'fr',
                    'target' => $targetLang,
                    'format' => 'text',
                ],
                'timeout' => 30,
            ]);

            $data = $response->toArray(false);

            return $data['translatedText'] ?? null;
        } catch (\Throwable) {
            return null;
        }
    }

    private function preservePlaceholders(string $original, string $translated): string
    {
        preg_match_all('/%\w+%/', $original, $matches);
        if (empty($matches[0])) {
            return $translated;
        }

        foreach ($matches[0] as $placeholder) {
            if (!str_contains($translated, $placeholder)) {
                $escaped = preg_quote($placeholder, '/');
                $pattern = '/'.str_replace(['%', '_'], ['%?', '[_ ]?'], $escaped).'/i';
                $translated = preg_replace($pattern, $placeholder, $translated, 1) ?? $translated;
            }
        }

        return $translated;
    }

    private function waitForApi(string $apiUrl, SymfonyStyle $io): bool
    {
        $io->text('Waiting for LibreTranslate at '.$apiUrl.'...');

        for ($i = 0; $i < 90; ++$i) {
            try {
                $response = $this->httpClient->request('GET', $apiUrl.'/languages', ['timeout' => 5]);
                if (200 === $response->getStatusCode()) {
                    return true;
                }
            } catch (\Throwable) {
                // API not ready yet, retry
            }
            sleep(2);
        }

        return false;
    }
}
