<?php

declare(strict_types=1);

namespace Autogedal\FeedMonitor\Console\Command;

use Autogedal\FeedMonitor\Model\Fetch\HttpClient;
use Autogedal\FeedMonitor\Model\Fetch\SimulatedHttpClient;
use Autogedal\FeedMonitor\Model\JobRegistry;
use Autogedal\FeedMonitor\Model\MonitorKey;
use Autogedal\FeedMonitor\Model\RunRepository;
use Magento\Cron\Model\ConfigInterface as CronConfig;
use Magento\Framework\App\Area;
use Magento\Framework\App\State;
use Magento\Framework\Console\Cli;
use Magento\Framework\ObjectManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Runs a monitored cron job now (tests, or right after allow-incomplete) and prints what the monitor recorded.
 * --feed-file / --http-code / --connection-error replace the feed's answer for this run only, to test a broken feed:
 * the command makes SimulatedHttpClient the HttpClient of its own process (nothing else is affected).
 */
class RunJobCommand extends Command
{
    public function __construct(
        private readonly JobRegistry $jobs,
        private readonly CronConfig $cronConfig,
        private readonly ObjectManagerInterface $objectManager,
        private readonly RunRepository $runs,
        private readonly State $state
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this->setName('autogedal:feed-monitor:run')
            ->setDescription('Feed monitor: run a monitored cron job now')
            ->addArgument('job', InputArgument::REQUIRED, 'Job code, e.g. alfaromtrans_paravanturi_stock')
            ->addOption('feed-file', null, InputOption::VALUE_REQUIRED, 'Test: use this local file as the feed answer')
            ->addOption('http-code', null, InputOption::VALUE_REQUIRED, 'Test: answer with this HTTP code (e.g. 503, 404)')
            ->addOption('connection-error', null, InputOption::VALUE_NONE, 'Test: the feed host does not answer (timeout)')
            ->addOption('only-url', null, InputOption::VALUE_REQUIRED, 'Test: simulate only for URLs containing this text (one sub-feed)');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $jobCode = (string)$input->getArgument('job');
        if (!$this->jobs->has($jobCode)) {
            $output->writeln('<error>Job necunoscut. Joburi urmarite: ' . implode(', ', $this->jobs->getJobCodes()) . '</error>');
            return Cli::RETURN_FAILURE;
        }
        $firebearId = \Autogedal\FeedMonitor\Model\Firebear\FirebearJobs::jobId($jobCode);
        if ($firebearId !== null) {
            $output->writeln('<error>Joburile Firebear se ruleaza cu: bin/magento import:job:run ' . $firebearId
                . ' (rezultatul ajunge in Feed Monitor prin evenimentele Firebear).</error>');
            return Cli::RETURN_FAILURE;
        }
        $definition = null;
        foreach ($this->cronConfig->getJobs() as $groupJobs) {
            $definition = $groupJobs[$jobCode] ?? $definition;
        }
        if (!$definition || empty($definition['instance'])) {
            $output->writeln('<error>Jobul nu este declarat in niciun crontab.xml.</error>');
            return Cli::RETURN_FAILURE;
        }
        try {
            $this->state->setAreaCode(Area::AREA_CRONTAB);
        } catch (\Throwable) {
            // already set
        }
        $simulated = $this->simulate($input, $output);
        if ($simulated === false) {
            return Cli::RETURN_FAILURE;
        }

        $output->writeln('Rulez ' . $definition['instance'] . '::' . ($definition['method'] ?? 'execute') . ' ...');
        $failed = false;
        try {
            $this->objectManager->create($definition['instance'])->{$definition['method'] ?? 'execute'}();
        } catch (\Throwable $e) {
            $output->writeln('<error>' . get_class($e) . ': ' . $e->getMessage() . '</error>');
            $failed = true;
        }
        if ($simulated instanceof SimulatedHttpClient && $simulated->getSimulatedRequests() === 0) {
            $output->writeln('<error>Simularea nu a fost folosita: jobul nu a citit niciun feed prin serviciul comun'
                . ' (sau --only-url nu se potriveste cu niciun URL).</error>');
            $failed = true;
        }

        foreach ($this->jobs->getFeedCodes($jobCode) ?: [''] as $feedCode) {
            $run = $this->runs->getLastRun($jobCode, $feedCode);
            $output->writeln(sprintf(
                '%s: %s%s, citite %s, actualizate %s%s',
                MonitorKey::build($jobCode, $feedCode),
                $run['status'] ?? 'nicio rulare',
                !empty($run['problem']) ? ' (' . $run['problem'] . ')' : '',
                $run['items_read'] ?? '-',
                $run['items_updated'] ?? '-',
                !empty($run['message']) ? "\n    " . $run['message'] : ''
            ));
        }

        return $failed ? Cli::RETURN_FAILURE : Cli::RETURN_SUCCESS;
    }

    /**
     * Replaces the feed answer when a test option is given.
     *
     * @return SimulatedHttpClient|bool the simulated client, true without test options, false when they are wrong
     */
    private function simulate(InputInterface $input, OutputInterface $output): SimulatedHttpClient|bool
    {
        $file = (string)$input->getOption('feed-file');
        $code = (int)$input->getOption('http-code');
        $urlPart = (string)$input->getOption('only-url');
        if ($input->getOption('connection-error')) {
            $client = $this->simulatedClient();
            $client->setResponse(null, 0, $urlPart);
            $output->writeln('<comment>Test: feed-ul nu raspunde (timeout simulat)' . ($urlPart !== '' ? ' pentru URL-urile cu "' . $urlPart . '"' : '') . '</comment>');
            return $client;
        }
        if ($file === '' && $code === 0) {
            return true;
        }
        if ($file !== '' && !is_readable($file)) {
            $output->writeln('<error>Fisierul ' . $file . ' nu poate fi citit.</error>');
            return false;
        }
        $body = $file !== '' ? (string)file_get_contents($file) : 'Simulated HTTP ' . $code;
        $client = $this->simulatedClient();
        $client->setResponse($body, $code ?: 200, $urlPart);
        $output->writeln(sprintf(
            '<comment>Test: raspuns simulat HTTP %d%s%s</comment>',
            $code ?: 200,
            $file !== '' ? ' cu continutul din ' . $file : '',
            $urlPart !== '' ? ' pentru URL-urile cu "' . $urlPart . '"' : ''
        ));

        return $client;
    }

    /**
     * SimulatedHttpClient as the HttpClient of this process: the job and the feed reader are created after this.
     */
    private function simulatedClient(): SimulatedHttpClient
    {
        $this->objectManager->configure(['preferences' => [HttpClient::class => SimulatedHttpClient::class]]);

        return $this->objectManager->get(SimulatedHttpClient::class);
    }
}
