<?php

namespace WPD\Diagnostics;

use WPD\Diagnostics\Checks\ConfigurationCheck;
use WPD\Diagnostics\Checks\CoreIntegrityCheck;
use WPD\Diagnostics\Checks\DatabaseCheck;
use WPD\Diagnostics\Checks\EnvironmentCheck;
use WPD\Diagnostics\Checks\ErrorLogCheck;
use WPD\Diagnostics\Checks\StructureCheck;
use WPD\Diagnostics\Checks\SyntaxCheck;
use WPD\Domain\Finding;
use WPD\Domain\ScanContext;

final class ScanOrchestrator
{
    private $checks;

    public function __construct(?array $checks = null)
    {
        $this->checks = $checks ?: array(
            new EnvironmentCheck(),
            new StructureCheck(),
            new ConfigurationCheck(),
            new ErrorLogCheck(),
            new DatabaseCheck(),
            new SyntaxCheck(),
            new CoreIntegrityCheck(),
        );
    }

    public function start($root)
    {
        return array(
            'schema' => 1,
            'scan_id' => bin2hex(random_bytes(8)),
            'index' => 0,
            'cursor' => array(),
            'findings' => array(),
            'checks' => array(),
            'complete' => false,
        );
    }

    public function step(array $state, ScanContext $context)
    {
        if (!empty($state['complete'])) {
            return $state;
        }
        $index = isset($state['index']) ? (int) $state['index'] : 0;
        if (!isset($this->checks[$index])) {
            $state['complete'] = true;
            $state['cursor'] = array();
            return $state;
        }

        $check = $this->checks[$index];
        try {
            $result = $check->run($context, isset($state['cursor']) && is_array($state['cursor']) ? $state['cursor'] : array());
            foreach ($result->findings() as $finding) {
                $state['findings'][] = $context->redactor()->data($finding);
            }
            $state['cursor'] = $result->cursor();
            if ($result->isComplete()) {
                $state['checks'][$check->id()] = array(
                    'label' => $check->label(),
                    'status' => $result->status(),
                );
                $state['index'] = $index + 1;
                $state['cursor'] = array();
            }
        } catch (\Throwable $error) {
            $reference = bin2hex(random_bytes(5));
            $finding = new Finding(
                'internal.' . $check->id(),
                'Una comprobación no pudo terminar',
                'advertencia',
                'media',
                'Referencia segura: ' . $reference . '.',
                'El resto del diagnóstico continuará, pero esta fase no ofrece cobertura completa.',
                array('Repite el análisis una vez.', 'Si vuelve a ocurrir, conserva la referencia y la versión de la herramienta para solicitar soporte.'),
                'Confirma en un nuevo análisis si la fase puede completarse.',
                'parcial'
            );
            $state['findings'][] = $finding->toArray();
            $state['checks'][$check->id()] = array(
                'label' => $check->label(),
                'status' => 'error de comprobación',
            );
            $state['index'] = $index + 1;
            $state['cursor'] = array();
        }

        if (!isset($this->checks[(int) $state['index']])) {
            $state['complete'] = true;
        }
        return $state;
    }

    public function progress(array $state)
    {
        $total = max(1, count($this->checks));
        $index = min($total, isset($state['index']) ? (int) $state['index'] : 0);
        $current = isset($this->checks[$index]) ? $this->checks[$index]->label() : 'Informe listo';
        return array(
            'complete' => !empty($state['complete']),
            'current' => $current,
            'done' => $index,
            'total' => $total,
            'percent' => (int) floor(($index / $total) * 100),
            'findings' => count(isset($state['findings']) ? $state['findings'] : array()),
        );
    }
}
