<?php

declare(strict_types=1);

namespace ShieldX\Scanner;

defined('ABSPATH') || exit;

final class ProgressiveDatabaseScanner
{
    private const BATCH_SIZE = 250;
    private const STORED_FINDINGS = 200;
    private const PHASES = [
        DatabaseScanner::DEEP_PHASE_OPTIONS,
        DatabaseScanner::DEEP_PHASE_POSTS,
        DatabaseScanner::DEEP_PHASE_POSTMETA,
        DatabaseScanner::DEEP_PHASE_COMMENTS,
        DatabaseScanner::DEEP_PHASE_ADMINISTRATORS,
    ];

    /** @var DatabaseScanner */
    private $scanner;

    public function __construct(DatabaseScanner $scanner)
    {
        $this->scanner = $scanner;
    }

    /**
     * @return array<string,mixed>
     */
    public function start(): array
    {
        if (!$this->scanner->available()) {
            throw new \RuntimeException('Database scanner is unavailable.');
        }

        $now = gmdate('c');

        return [
            'engine_version' => DatabaseScanner::DEEP_ENGINE_VERSION,
            'status' => 'running',
            'phase' => self::PHASES[0],
            'phase_index' => 0,
            'cursor' => '',
            'items_scanned' => 0,
            'issues_found' => 0,
            'findings' => [],
            'active_threat_ids' => [],
            'phase_active_threat_ids' => [],
            'phase_totals' => [],
            'administrator_ids' => [],
            'started_at' => $now,
            'updated_at' => $now,
            'completed_at' => '',
            'error' => '',
        ];
    }

    /**
     * @param array<string,mixed> $state
     * @return array{state:array<string,mixed>,batch:array<string,mixed>,completed_phase:string,completed_phase_active_threat_ids:array<int,string>}
     */
    public function step(array $state, int $batchSize = self::BATCH_SIZE): array
    {
        if ((string) ($state['status'] ?? '') !== 'running') {
            return [
                'state' => $state,
                'batch' => [],
                'completed_phase' => '',
                'completed_phase_active_threat_ids' => [],
            ];
        }

        $phaseIndex = max(0, (int) ($state['phase_index'] ?? 0));
        $phase = self::PHASES[$phaseIndex] ?? '';
        if ($phase === '') {
            $state['status'] = 'completed';
            $state['completed_at'] = gmdate('c');

            return [
                'state' => $state,
                'batch' => [],
                'completed_phase' => '',
                'completed_phase_active_threat_ids' => [],
            ];
        }

        try {
            $batch = $this->scanner->scanDeepBatch($phase, $state['cursor'] ?? 0, $batchSize);
            $state['cursor'] = $batch['cursor'] ?? $state['cursor'] ?? 0;
            $state['items_scanned'] = (int) ($state['items_scanned'] ?? 0) + (int) ($batch['items_scanned'] ?? 0);
            $state['issues_found'] = (int) ($state['issues_found'] ?? 0) + (int) ($batch['issues_found'] ?? 0);
            $state['updated_at'] = gmdate('c');

            $findings = isset($state['findings']) && is_array($state['findings']) ? $state['findings'] : [];
            $batchIssues = isset($batch['issues']) && is_array($batch['issues']) ? array_values(array_filter($batch['issues'], 'is_array')) : [];
            $state['findings'] = array_slice(array_merge($findings, $batchIssues), 0, self::STORED_FINDINGS);

            $activeThreatIds = isset($state['active_threat_ids']) && is_array($state['active_threat_ids']) ? array_map('strval', $state['active_threat_ids']) : [];
            $batchThreatIds = isset($batch['active_threat_ids']) && is_array($batch['active_threat_ids']) ? array_map('strval', $batch['active_threat_ids']) : [];
            $state['active_threat_ids'] = array_values(array_unique(array_filter(array_merge($activeThreatIds, $batchThreatIds))));

            $phaseThreatIds = isset($state['phase_active_threat_ids']) && is_array($state['phase_active_threat_ids']) ? array_map('strval', $state['phase_active_threat_ids']) : [];
            $state['phase_active_threat_ids'] = array_values(array_unique(array_filter(array_merge($phaseThreatIds, $batchThreatIds))));

            $phaseTotals = isset($state['phase_totals']) && is_array($state['phase_totals']) ? $state['phase_totals'] : [];
            $phaseTotals[$phase] = (int) ($phaseTotals[$phase] ?? 0) + (int) ($batch['items_scanned'] ?? 0);
            $state['phase_totals'] = $phaseTotals;

            if ($phase === DatabaseScanner::DEEP_PHASE_ADMINISTRATORS) {
                $adminIds = isset($state['administrator_ids']) && is_array($state['administrator_ids']) ? array_map('intval', $state['administrator_ids']) : [];
                $batchAdminIds = isset($batch['administrator_ids']) && is_array($batch['administrator_ids']) ? array_map('intval', $batch['administrator_ids']) : [];
                $state['administrator_ids'] = array_values(array_unique(array_filter(array_merge($adminIds, $batchAdminIds))));
            }

            $completedPhase = '';
            $completedPhaseThreatIds = [];
            if (!empty($batch['phase_complete'])) {
                $completedPhase = $phase;
                $completedPhaseThreatIds = isset($state['phase_active_threat_ids']) && is_array($state['phase_active_threat_ids'])
                    ? array_values(array_unique(array_filter(array_map('strval', $state['phase_active_threat_ids']))))
                    : [];
                $phaseIndex++;
                $state['phase_index'] = $phaseIndex;
                $state['phase_active_threat_ids'] = [];
                $state['cursor'] = $phaseIndex === 0 ? '' : 0;
                $state['phase'] = self::PHASES[$phaseIndex] ?? 'completed';

                if ($phaseIndex >= count(self::PHASES)) {
                    $this->scanner->completeDeepAdministratorBaseline((array) ($state['administrator_ids'] ?? []));
                    $state['status'] = 'completed';
                    $state['completed_at'] = gmdate('c');
                }
            }

            return [
                'state' => $state,
                'batch' => $batch,
                'completed_phase' => $completedPhase,
                'completed_phase_active_threat_ids' => $completedPhaseThreatIds,
            ];
        } catch (\Throwable $error) {
            $state['status'] = 'failed';
            $state['error'] = $error->getMessage();
            $state['updated_at'] = gmdate('c');

            return [
                'state' => $state,
                'batch' => [],
                'completed_phase' => '',
                'completed_phase_active_threat_ids' => [],
            ];
        }
    }

    /**
     * @param array<string,mixed> $state
     * @return array<string,mixed>
     */
    public function status(array $state): array
    {
        return [
            'engine_version' => (string) ($state['engine_version'] ?? DatabaseScanner::DEEP_ENGINE_VERSION),
            'status' => (string) ($state['status'] ?? 'failed'),
            'phase' => (string) ($state['phase'] ?? ''),
            'phase_index' => (int) ($state['phase_index'] ?? 0),
            'phases_total' => count(self::PHASES),
            'cursor' => $state['cursor'] ?? 0,
            'items_scanned' => (int) ($state['items_scanned'] ?? 0),
            'issues_found' => (int) ($state['issues_found'] ?? 0),
            'started_at' => (string) ($state['started_at'] ?? ''),
            'updated_at' => (string) ($state['updated_at'] ?? ''),
            'completed_at' => (string) ($state['completed_at'] ?? ''),
            'error' => (string) ($state['error'] ?? ''),
        ];
    }

    /**
     * @param array<string,mixed> $state
     * @return array<string,mixed>
     */
    public function cancel(array $state): array
    {
        if ((string) ($state['status'] ?? '') === 'running') {
            $state['status'] = 'cancelled';
            $state['updated_at'] = gmdate('c');
        }

        return $state;
    }

    /**
     * @param array<string,mixed> $state
     * @return array<string,mixed>
     */
    public function result(array $state): array
    {
        return [
            'engine_version' => (string) ($state['engine_version'] ?? DatabaseScanner::DEEP_ENGINE_VERSION),
            'available' => true,
            'status' => (string) ($state['status'] ?? 'failed'),
            'depth' => 'deep',
            'items_scanned' => (int) ($state['items_scanned'] ?? 0),
            'issues_found' => (int) ($state['issues_found'] ?? 0),
            'active_threat_ids' => isset($state['active_threat_ids']) && is_array($state['active_threat_ids']) ? array_values(array_unique(array_filter(array_map('strval', $state['active_threat_ids'])))) : [],
            'phase_totals' => isset($state['phase_totals']) && is_array($state['phase_totals']) ? $state['phase_totals'] : [],
            'issues' => isset($state['findings']) && is_array($state['findings']) ? array_values(array_filter($state['findings'], 'is_array')) : [],
            'error' => (string) ($state['error'] ?? ''),
        ];
    }
}
