<?php

namespace Dalten\RuianToUir\Command;

use Dalten\RuianToUir\Command\Helper\FileDownloader;
use Dalten\RuianToUir\Converter\CodebookItem;
use Dalten\RuianToUir\Converter\Result\CodebookItem as CodebookItemResult;
use Symfony\Component\Console\Command\Command as BaseCommand;
use Symfony\Component\Console\Input\InputArgument;
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\Yaml\Yaml;

class GetCodebooks extends BaseCommand
{
    private CodebookItem $converter;
    /**
     * @var string[]
     */
    private array $excludedCodebooks;
    private FileDownloader $downloader;
    private string $sourceFileListPath;

    public function __construct(FileDownloader $downloader, CodebookItem $converter, string $sourceFileListPath,
        array $excludedCodebooks)
    {
        parent::__construct();

        $this->converter = $converter;
        $this->excludedCodebooks = $excludedCodebooks;
        $this->downloader = $downloader;
        $this->sourceFileListPath = $sourceFileListPath;
    }

    public function execute(InputInterface $input, OutputInterface $output)
    {
        $io = new SymfonyStyle($input, $output);

        $deleteAfter = false;
        /** @var string|null $filename */
        $filename = $input->getArgument('input-file');
        if (!isset($filename)) {
            $uri = $this->downloader->getFirstLineFromFile($this->sourceFileListPath);
            $filename = $this->downloader->downloadAndGunzipFileToTemp($uri);
            $deleteAfter = true;
        }
        $items = $this->handleFile($filename);
        if ($deleteAfter) {
            unlink($filename);
        }

        /** @var string $format */
        $format = $input->getOption('output-format');
        switch ($format) {
            case 'yml':
                $this->outputYaml($items, $io);

                break;
            case 'txt':
                $this->outputText($items, $io);

                break;
            case 'php':
                $this->outputPhp($items, $io);

                break;
            default:
                $io->error(sprintf('Neznámý formát "%s". (povolené: txt, yml, php)', $format));

                return 1;
        }

        return 0;
    }

    private function handleFile(string $filename): array
    {
        $reader = new \XMLReader();
        if (false === $reader->open($filename)) {
            throw new \InvalidArgumentException(sprintf('Soubor %s nelze otevřít!', $filename));
        }

        $codebooks = [];

        // XMLReader přeskakuje některé tagy bez innerText
        // pokud se tomu povede zabránit, pak vše s $fallbackContainerTagName může jít pryč
        $fallbackContainerTagName = '';
        while ($reader->read()) {
            if ('vf:Hlavicka' == $reader->name || in_array($reader->name, $this->excludedCodebooks)) {
                $reader->next();
            }

            if (\XMLReader::ELEMENT === $reader->nodeType) {
                if ('vf' === $reader->prefix) {
                    $fallbackContainerTagName = $reader->name;
                }
                if ($this->converter->accepts($reader)) {
                    if (in_array($fallbackContainerTagName, $this->excludedCodebooks)) {
                        $reader->read();
                    }
                    $item = $this->converter->convert($reader, $fallbackContainerTagName);
                    $name = $item->getCodebookName();
                    if (!isset($codebooks[$name])) {
                        $codebooks[$name] = [];
                    }
                    $codebooks[$name][$item->getValue()] = $item;
                } else {
                    $reader->read();
                }
            }
        }

        $reader->close();

        return $codebooks;
    }

    /**
     * @param array<string, CodebookItem[]> $items
     */
    private function outputYaml(array $items, SymfonyStyle $io): void
    {
        $data = [];

        foreach ($items as $codebookName => $options) {
            /** @var CodebookItemResult[] $options */
            $convertedOptions = [];

            foreach ($options as $option) {
                $convertedOptions[] = [
                    'value' => $option->getValue(),
                    'label' => $option->getLabel(),
                    'valid' => $option->isValid(),
                ];
            }

            $data[$codebookName] = $convertedOptions;
        }

        $yaml = new Yaml();
        $io->write($yaml->dump($data));
    }

    /**
     * @param array<string, CodebookItem[]> $items
     */
    private function outputPhp(array $items, SymfonyStyle $io): void
    {
        $data = [];

        foreach ($items as $codebookName => $options) {
            /** @var CodebookItemResult[] $options */
            $convertedOptions = [];

            foreach ($options as $option) {
                if ($option->isValid()) {
                    $convertedOptions[$option->getValue()] = $option->getLabel();
                }
            }

            $data[$codebookName] = $convertedOptions;
        }

        $io->write('<?php' . PHP_EOL . PHP_EOL . 'return ' . var_export($data, true));
    }

    /**
     * @param array<string, CodebookItem[]> $items
     */
    private function outputText(array $items, SymfonyStyle $io)
    {
        foreach ($items as $codebookName => $options) {
            /** @var CodebookItemResult[] $options */
            $io->section($codebookName);
            $rows = [];
            $headers = ['value', 'label', 'valid'];

            foreach ($options as $option) {
                $rows[] = [$option->getValue(), $option->getLabel(), $option->isValid() ? 'Y' : 'N'];
            }

            $io->table($headers, $rows);
        }
    }

    protected function configure()
    {
        $this->setName('get-codebooks');
        $this->setDescription('Získá a vypíše aktuální číselníky.');
        $this->addArgument(
            'input-file', InputArgument::OPTIONAL, 'Zdroj dat (cesta k souboru). [default: stáhnout do temp]', null
        );
        $this->addOption(
            'output-format', null, InputOption::VALUE_REQUIRED, 'Formát dat. (txt, yml, php)', 'txt'
        );
    }

    private function skipToElementEnd(\XMLReader $document): \XMLReader
    {
        $elementName = $document->name;
        $depth = 1;
        while ($depth > 0) {
            $document->read();
            if ($document->name === $elementName && \XMLReader::END_ELEMENT === $document->nodeType) {
                --$depth;
            } elseif ($document->name === $elementName && \XMLReader::ELEMENT === $document->nodeType) {
                ++$depth;
            }
        }

        return $document;
    }
}
