<?php
/**
 * Copyright 2020 Adobe
 * All Rights Reserved.
 *
 * NOTICE: Adobe permits you to use, modify, and distribute this file in
 * accordance with the terms of the Adobe license agreement accompanying
 * it.
 */
declare(strict_types=1);

namespace Sut\Infrastructure\Entrypoint\Command;

use DateTime;
use Exception;
use JsonException;
use RuntimeException;
use Sut\Domain\FileSystem\FileSystemInterface;
use Sut\Domain\Issue\DTO\IssueLevel;
use Sut\Domain\OutputFormat\Console\OutputFormatInterface as ConsoleOutputFormatInterface;
use Sut\Domain\OutputFormat\File\OutputFormatInterface as ExportOutputFormatInterface;
use Sut\Domain\Statistics\Statistics;
use Sut\Infrastructure\GraphQl\GraphQl;
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;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Style\SymfonyStyle;

class GraphQlCompareCommand extends Command
{
    /**
     * @var string
     */
    protected static $defaultName = 'graphql:compare';

    /**
     * @var GraphQl
     */
    private $graphQl;

    /**
     * @var ConsoleOutputFormatInterface
     */
    private $consoleOutputFormat;

    /**
     * @var ExportOutputFormatInterface
     */
    private $exportOutputFormat;

    /**
     * @var string
     */
    private $schemaVerificationPath;

    /**
     * @var FileSystemInterface
     */
    private $fileSystem;

    /**
     * @var string
     */
    private $outputPath;

    /**
     * @var string
     */
    private $errorCodesDocumentation;

    /**
     * @var string
     */
    private $documentationLink;

    /**
     * @param GraphQl $graphQl
     * @param ConsoleOutputFormatInterface $consoleOutputFormat
     * @param ExportOutputFormatInterface $exportOutputFormat
     * @param string $schemaVerificationPath
     * @param FileSystemInterface $fileSystem
     * @param string $outputPath
     * @param string $errorCodesDocumentation
     * @param string $documentationLink
     */
    public function __construct(
        GraphQl $graphQl,
        ConsoleOutputFormatInterface $consoleOutputFormat,
        ExportOutputFormatInterface $exportOutputFormat,
        string $schemaVerificationPath,
        FileSystemInterface $fileSystem,
        string $outputPath,
        string $errorCodesDocumentation,
        string $documentationLink
    ) {
        parent::__construct(self::$defaultName);
        $this->graphQl = $graphQl;
        $this->consoleOutputFormat = $consoleOutputFormat;
        $this->exportOutputFormat = $exportOutputFormat;
        $this->schemaVerificationPath = $schemaVerificationPath;
        $this->fileSystem = $fileSystem;
        $this->outputPath = sprintf($outputPath, (new DateTime())->format('d_M_Y_h:i'));
        $this->errorCodesDocumentation = $errorCodesDocumentation;
        $this->documentationLink = $documentationLink;
    }

    /**
     * @inheritdoc
     */
    protected function configure(): void
    {
        $this->setDescription('GraphQL schema compatibility verification')
            ->addArgument(
                'schema1',
                InputArgument::REQUIRED,
                'Endpoint URL pointing to the first GraphQL schema.'
            )->addArgument(
                'schema2',
                InputArgument::REQUIRED,
                'Endpoint URL pointing to the second GraphQL schema.'
            )->addOption(
                'output',
                'o',
                InputOption::VALUE_OPTIONAL,
                "Path of the file where the output will be exported (JSON Format)"
            );
    }

    /**
     * @inheritdoc
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $io->getErrorStyle()->writeln([
            'Upgrade compatibility tool - GraphQL Compare',
            '',
            sprintf(
                'Check <options=bold>%s</> for a detailed documentation of the Upgrade Compatibility Tool.',
                $this->documentationLink
            ),
            ''
        ]);

        $summary = new Statistics();

        try {
            $summary->setStartTime(new DateTime());
            $graphQlSchemaDiff = $this->graphQl->getSchemaDiff(
                $input->getArgument('schema1'),
                $input->getArgument('schema2'),
                $this->schemaVerificationPath
            );
            $summary->setEndTime(new DateTime());
        } catch (Exception $e) {
            $io->getErrorStyle()->writeln('<error>' . $e->getMessage() . '</error>');
            return Command::FAILURE;
        }

        $custom = [
            'warnings' => [
                'issues' => []
            ],
            'critical' => [
                'issues' => []
            ]
        ];

        if ($graphQlSchemaDiff) {
            foreach ($graphQlSchemaDiff as $graphQlIssue) {
                if ($graphQlIssue->getLevel() === IssueLevel::WARNING) {
                    $summary->setNumberOfWarnings(
                        'graphql',
                        $summary->getNumberOfWarnings('graphql') + 1
                    );
                    if (!array_key_exists($graphQlIssue->getIssueType(), $custom['warnings']['issues'])) {
                        $custom['warnings']['issues'][$graphQlIssue->getIssueType()] = 1;
                    } else {
                        $custom['warnings']['issues'][$graphQlIssue->getIssueType()]++;
                    }
                }
                if ($graphQlIssue->getLevel() === IssueLevel::CRITICAL) {
                    $summary->setNumberOfCriticals(
                        'graphql',
                        $summary->getNumberOfCriticals('graphql') + 1
                    );
                    if (!array_key_exists($graphQlIssue->getIssueType(), $custom['critical']['issues'])) {
                        $custom['critical']['issues'][$graphQlIssue->getIssueType()] = 1;
                    } else {
                        $custom['critical']['issues'][$graphQlIssue->getIssueType()]++;
                    }
                }
            }
        }
        $this->consoleOutputFormat->format($io, $graphQlSchemaDiff);
        $summary->setCustom($custom);

        if ($graphQlSchemaDiff) {
            $output->writeln(sprintf(
                '<comment>Check %s for a detailed list of Upgrade Compatibility Tool errors.</comment>',
                $this->errorCodesDocumentation
            ));
            $this->consoleOutputFormat->showStatistics($io, $summary);
        }

        $outputPath = $input->getOption('output') ?? $this->outputPath;
        if ($outputPath) {
            try {
                $this->export($outputPath, $graphQlSchemaDiff, $summary);
            } catch (RuntimeException $exception) {
                $io->getErrorStyle()->writeln(
                    sprintf(
                        '<error>Could not create the exported file in the %s</error>',
                        $outputPath
                    )
                );
            }
        }

        return Command::SUCCESS;
    }

    /**
     * @param string $outputPath
     * @param array $graphQlSchemaDiff
     * @param Statistics $summary
     * @throws RuntimeException
     */
    protected function export(string $outputPath, array $graphQlSchemaDiff, Statistics $summary): void
    {
        $this->fileSystem->createDirectory($outputPath);
        // phpcs:ignore Generic.PHP.NoSilencedErrors
        $file = @$this->fileSystem->openFile($outputPath);
        if ($file === false) {
            throw new RuntimeException(
                sprintf(
                    'File "%s" was not created, because you do not have write permissions',
                    $outputPath
                )
            );
        }
        $output = new StreamOutput($file);

        $this->exportOutputFormat->export(
            $output,
            $graphQlSchemaDiff,
            $summary
        );
    }
}
