<?php

/**
 * @package    Grav\Console\Cli
 *
 * @copyright  Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
 * @license    MIT License; see LICENSE file for details.
 */

namespace Grav\Console\Cli;

use Grav\Common\Config\Config;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Uri;
use Grav\Console\ConsoleCommand;
use RocketTheme\Toolbox\Event\Event;
use Symfony\Component\Console\Input\InputOption;

class PageSystemValidatorCommand extends ConsoleCommand
{
    protected $tests = [
        // Content
        'header' => [[]],
        'summary' => [[], [200], [200, true]],
        'content' => [[]],
        'getRawContent' => [[]],
        'rawMarkdown' => [[]],
        'value' => [['content'], ['route'], ['order'], ['ordering'], ['folder'], ['slug'], ['name'], /*['frontmatter'],*/ ['header.menu'], ['header.slug']],
        'title' => [[]],
        'menu' => [[]],
        'visible' => [[]],
        'published' => [[]],
        'publishDate' => [[]],
        'unpublishDate' => [[]],
        'process' => [[]],
        'slug' => [[]],
        'order' => [[]],
        //'id' => [[]],
        'modified' => [[]],
        'lastModified' => [[]],
        'folder' => [[]],
        'date' => [[]],
        'dateformat' => [[]],
        'taxonomy' => [[]],
        'shouldProcess' => [['twig'], ['markdown']],
        'isPage' => [[]],
        'isDir' => [[]],
        'exists' => [[]],

        // Forms
        'forms' => [[]],

        // Routing
        'urlExtension' => [[]],
        'routable' => [[]],
        'link' => [[], [false], [true]],
        'permalink' => [[]],
        'canonical' => [[], [false], [true]],
        'url' => [[], [true], [true, true], [true, true, false], [false, false, true, false]],
        'route' => [[]],
        'rawRoute' => [[]],
        'routeAliases' => [[]],
        'routeCanonical' => [[]],
        'redirect' => [[]],
        'relativePagePath' => [[]],
        'path' => [[]],
        //'folder' => [[]],
        'parent' => [[]],
        'topParent' => [[]],
        'currentPosition' => [[]],
        'active' => [[]],
        'activeChild' => [[]],
        'home' => [[]],
        'root' => [[]],

        // Translations
        'translatedLanguages' => [[], [false], [true]],
        'untranslatedLanguages' => [[], [false], [true]],
        'language' => [[]],

        // Legacy
        'raw' => [[]],
        'frontmatter' => [[]],
        'httpResponseCode' => [[]],
        'httpHeaders' => [[]],
        'blueprintName' => [[]],
        'name' => [[]],
        'childType' => [[]],
        'template' => [[]],
        'templateFormat' => [[]],
        'extension' => [[]],
        'expires' => [[]],
        'cacheControl' => [[]],
        'ssl' => [[]],
        'metadata' => [[]],
        'eTag' => [[]],
        'filePath' => [[]],
        'filePathClean' => [[]],
        'orderDir' => [[]],
        'orderBy' => [[]],
        'orderManual' => [[]],
        'maxCount' => [[]],
        'modular' => [[]],
        'modularTwig' => [[]],
        //'children' => [[]],
        'isFirst' => [[]],
        'isLast' => [[]],
        'prevSibling' => [[]],
        'nextSibling' => [[]],
        'adjacentSibling' => [[]],
        'ancestor' => [[]],
        //'inherited' => [[]],
        //'inheritedField' => [[]],
        'find' => [['/']],
        //'collection' => [[]],
        //'evaluate' => [[]],
        'folderExists' => [[]],
        //'getOriginal' => [[]],
        //'getAction' => [[]],
    ];

    /** @var Grav */
    protected $grav;

    protected function configure()
    {
        $this
            ->setName('page-system-validator')
            ->setDescription('Page validator can be used to compare site before/after update and when migrating to Flex Pages.')
            ->addOption('record', 'r', InputOption::VALUE_NONE, 'Record results')
            ->addOption('check', 'c', InputOption::VALUE_NONE, 'Compare site against previously recorded results')
            ->setHelp('The <info>page-system-validator</info> command can be used to test the pages before and after upgrade');
    }

    protected function serve()
    {
        $this->output->writeln('');

        $this->grav = $grav = Grav::instance();
        $grav->setup();

        // Initialize
        $grav['uri']->init();
        /** @var Config $config */
        $config = $grav['config'];
        $config->init();
        $grav['plugins']->setup();
        $grav['debugger']->init();
                // Initialize the timezone.
        $timezone = $config->get('system.timezone');
        if ($timezone) {
            date_default_timezone_set($timezone);
        }
        /** @var Uri $uri */
        $uri = $grav['uri'];
        $uri->init();
        $grav->setLocale();
        $grav['language']->setActive('en');

        // Plugins
        $grav['accounts'];
        $grav['plugins']->init();
        $grav->fireEvent('onPluginsInitialized');

        // Themes
        $grav['themes']->init();

        // Twig
        $grav['twig']->init();

        // Pages
        $grav['pages']->init();
        $grav->fireEvent('onPagesInitialized', new Event(['pages' => $grav['pages']]));
        $grav->fireEvent('onPageInitialized', new Event(['page' => $grav['page']]));

        if ($this->input->getOption('record')) {
            $this->output->writeln('Pages: ' . $config->get('system.pages.type', 'page'));

            $this->output->writeln('<magenta>Record tests</magenta>');
            $this->output->writeln('');

            $results = $this->record();
            $file = $this->getFile('pages-old');
            $file->save($results);

            $this->output->writeln('Recorded tests to ' . $file->filename());
        } elseif ($this->input->getOption('check')) {
            $this->output->writeln('Pages: ' . $config->get('system.pages.type', 'page'));

            $this->output->writeln('<magenta>Run tests</magenta>');
            $this->output->writeln('');

            $new = $this->record();
            $file = $this->getFile('pages-new');
            $file->save($new);
            $this->output->writeln('Recorded tests to ' . $file->filename());

            $file = $this->getFile('pages-old');
            $old = $file->content();

            $results = $this->check($old, $new);
            $file = $this->getFile('diff');
            $file->save($results);
            $this->output->writeln('Recorded results to ' . $file->filename());
        } else {
            $this->output->writeln('<green>page-system-validator [-r|--record] [-c|--check]</green>');
        }
        $this->output->writeln('');
    }

    private function record()
    {
        $pages = $this->grav['pages'];
        $all = $pages->all();

        $results = [];
        foreach ($all as $page) {
            foreach ($this->tests as $method => $params) {
                $params = $params ?: [[]];
                foreach ($params as $p) {
                    $result = $page->$method(...$p);
                    if (in_array($method, ['summary', 'content', 'getRawContent'], true)) {
                        $result = preg_replace('/name="(form-nonce|__unique_form_id__)" value="[^"]+"/', 'name="\\1" value="DYNAMIC"', $result);
                        $result = preg_replace('`src=("|\'|&quot;)/images/./././././[^"]+\\1`', 'src="\\1images/GENERATED\\1', $result);
                        $result = preg_replace('/\?\d{10}/', '?1234567890', $result);
                    } elseif ($method === 'httpHeaders' && isset($result['Expires'])) {
                        $result['Expires'] = 'Thu, 19 Sep 2019 13:10:24 GMT (REPLACED AS DYNAMIC)';
                    } elseif ($result instanceof PageInterface) {
                        $result = $result->rawRoute();
                    } elseif (is_object($result)) {
                        $result = json_decode(json_encode($result), true);
                    }

                    $ps = [];
                    foreach ($p as $val) {
                        $ps[] = (string)var_export($val, true);
                    }
                    $pstr = implode(', ', $ps);
                    $call = "->{$method}({$pstr})";
                    $results[$page->rawRoute()][$call] = $result;
                }
            }
        }

        return json_decode(json_encode($results), true);
    }

    private function check(array $old, array $new)
    {
        $errors = [];
        foreach ($old as $path => $page) {
            if (!isset($new[$path])) {
                $errors[$path] = 'PAGE REMOVED';
                continue;
            }
            foreach ($page as $method => $test) {
                if (($new[$path][$method] ?? null) !== $test) {
                    $errors[$path][$method] = ['old' => $test, 'new' => $new[$path][$method]];
                }
            }
        }

        return $errors;
    }

    /**
     * @param string $name
     * @return CompiledYamlFile
     */
    private function getFile(string $name)
    {
        return CompiledYamlFile::instance('cache://tests/' . $name . '.yaml');
    }
}
