<?php

namespace VulnTitan\Cli;

use Throwable;
use VulnTitan\Services\BaselineBuilder;
use VulnTitan\Services\FileIntegrityScanner;
use VulnTitan\Services\MalwareScanner;
use VulnTitan\Services\VulnerabilityScanner;
use WP_CLI;
use WP_CLI_Command;
use WP_CLI\Utils;

class ScanCommand extends WP_CLI_Command
{
    /**
     * Run a malware scan.
     *
     * ## OPTIONS
     *
     * [--scope=<scope>]
     * : File scope to scan.
     * ---
     * default: all
     * options:
     *   - all
     *   - wp-content
     *   - plugins
     *   - themes
     * ---
     *
     * [--format=<format>]
     * : Output format.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     * ---
     *
     * [--fail-on-findings]
     * : Exit with code 1 when malware findings or file errors are detected.
     */
    public function malware(array $args, array $assoc_args): void
    {
        $format = $this->getFormat($assoc_args);
        $result = $this->runMalwareScan($this->getScope($assoc_args), $format !== 'json');

        $this->renderMalwareResult($result, $format);
        $this->finishCommand(
            'Malware scan',
            $this->hasMalwareIssues($result),
            $this->shouldFailOnFindings($assoc_args),
            $format === 'json'
        );
    }

    /**
     * Run an integrity scan.
     *
     * ## OPTIONS
     *
     * [--format=<format>]
     * : Output format.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     * ---
     *
     * [--fail-on-findings]
     * : Exit with code 1 when integrity issues or file errors are detected.
     */
    public function integrity(array $args, array $assoc_args): void
    {
        $format = $this->getFormat($assoc_args);
        $result = $this->runIntegrityScan($format !== 'json');

        $this->renderIntegrityResult($result, $format);
        $this->finishCommand(
            'Integrity scan',
            $this->hasIntegrityIssues($result),
            $this->shouldFailOnFindings($assoc_args),
            $format === 'json'
        );
    }

    /**
     * Run a vulnerability scan.
     *
     * ## OPTIONS
     *
     * [--format=<format>]
     * : Output format.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     * ---
     *
     * [--fail-on-findings]
     * : Exit with code 1 when vulnerabilities or API errors are detected.
     */
    public function vulnerability(array $args, array $assoc_args): void
    {
        $format = $this->getFormat($assoc_args);
        $result = $this->runVulnerabilityScan($format !== 'json');

        $this->renderVulnerabilityResult($result, $format);
        $this->finishCommand(
            'Vulnerability scan',
            $this->hasVulnerabilityIssues($result),
            $this->shouldFailOnFindings($assoc_args),
            $format === 'json'
        );
    }

    /**
     * Run all scans in sequence.
     *
     * ## OPTIONS
     *
     * [--scope=<scope>]
     * : Malware scan file scope.
     * ---
     * default: all
     * options:
     *   - all
     *   - wp-content
     *   - plugins
     *   - themes
     * ---
     *
     * [--format=<format>]
     * : Output format.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     * ---
     *
     * [--fail-on-findings]
     * : Exit with code 1 when any scan reports findings or errors.
     */
    public function all(array $args, array $assoc_args): void
    {
        $format = $this->getFormat($assoc_args);
        $scope = $this->getScope($assoc_args);

        $results = [
            'malware' => $this->runMalwareScan($scope, $format !== 'json'),
            'integrity' => $this->runIntegrityScan($format !== 'json'),
            'vulnerability' => $this->runVulnerabilityScan($format !== 'json'),
        ];

        if ($format === 'json') {
            $this->outputJson($results);
        } else {
            WP_CLI::line('');
            WP_CLI::line('Malware');
            WP_CLI::line('-------');
            $this->renderMalwareResult($results['malware'], 'table');

            WP_CLI::line('');
            WP_CLI::line('Integrity');
            WP_CLI::line('---------');
            $this->renderIntegrityResult($results['integrity'], 'table');

            WP_CLI::line('');
            WP_CLI::line('Vulnerability');
            WP_CLI::line('-------------');
            $this->renderVulnerabilityResult($results['vulnerability'], 'table');
        }

        $hasIssues = $this->hasMalwareIssues($results['malware'])
            || $this->hasIntegrityIssues($results['integrity'])
            || $this->hasVulnerabilityIssues($results['vulnerability']);

        $this->finishCommand(
            'Combined scan',
            $hasIssues,
            $this->shouldFailOnFindings($assoc_args),
            $format === 'json'
        );
    }

    protected function runMalwareScan(string $scope, bool $showProgress): array
    {
        $scanner = new MalwareScanner($scope);
        $files = $scanner->getFilesToScan();
        $total = count($files);
        $progress = $showProgress && $total > 0
            ? Utils\make_progress_bar('Scanning files for malware', $total)
            : null;

        $summary = [
            'scope' => $scanner->getScope(),
            'total_files' => $total,
            'processed' => 0,
            'clean' => 0,
            'infected' => 0,
            'error' => 0,
        ];
        $issues = [];

        foreach ($files as $filePath) {
            try {
                $findings = $scanner->scanSingleFile($filePath);
                $status = !empty($findings) ? 'infected' : 'clean';
                $summary[$status]++;

                if ($status === 'infected') {
                    $issues[] = [
                        'file' => $this->normalizeRelativePath($filePath),
                        'status' => $status,
                        'severity' => $this->highestMalwareSeverity($findings),
                        'findings' => count($findings),
                        'patterns' => $this->collectMalwarePatterns($findings),
                        'error' => '',
                        'details' => $findings,
                    ];
                }
            } catch (Throwable $e) {
                $summary['error']++;
                $issues[] = [
                    'file' => $this->normalizeRelativePath($filePath),
                    'status' => 'error',
                    'severity' => 'error',
                    'findings' => 0,
                    'patterns' => '',
                    'error' => $e->getMessage(),
                    'details' => [],
                ];
            }

            $summary['processed']++;

            if ($progress) {
                $progress->tick();
            }
        }

        if ($progress) {
            $progress->finish();
        }

        return [
            'summary' => $summary,
            'issues' => $issues,
        ];
    }

    protected function runIntegrityScan(bool $showProgress): array
    {
        $baselineGenerated = false;

        if (!BaselineBuilder::exists()) {
            if (!BaselineBuilder::save()) {
                WP_CLI::error('Baseline generation failed.');
            }

            update_option('vulntitan_baseline_generated_at', current_time('mysql'));
            $baselineGenerated = true;
        }

        $scanner = new FileIntegrityScanner();
        $scanner->loadCoreHashes();
        $scanner->loadBaseline();

        $paths = $scanner->collectQueuePaths(true);
        $total = count($paths);
        $progress = $showProgress && $total > 0
            ? Utils\make_progress_bar('Scanning files for integrity issues', $total)
            : null;

        $summary = [
            'baseline_generated' => $baselineGenerated ? 'yes' : 'no',
            'total_files' => $total,
            'processed' => 0,
            'ok' => 0,
            'modified' => 0,
            'missing' => 0,
            'unexpected' => 0,
            'error' => 0,
            'unknown' => 0,
        ];
        $issues = [];

        foreach ($paths as $relativePath) {
            try {
                $scanResult = $scanner->scanSingleFile($relativePath);
                $status = strtolower((string) ($scanResult['status'] ?? 'unknown'));

                if (!array_key_exists($status, $summary)) {
                    $status = 'unknown';
                }

                $summary[$status]++;

                if ($status !== 'ok') {
                    $issues[] = [
                        'file_path' => $relativePath,
                        'status' => $status,
                        'is_core' => !empty($scanResult['is_core']) ? 'yes' : 'no',
                        'error' => '',
                    ];
                }
            } catch (Throwable $e) {
                $summary['error']++;
                $issues[] = [
                    'file_path' => $relativePath,
                    'status' => 'error',
                    'is_core' => 'no',
                    'error' => $e->getMessage(),
                ];
            }

            $summary['processed']++;

            if ($progress) {
                $progress->tick();
            }
        }

        if ($progress) {
            $progress->finish();
        }

        return [
            'summary' => $summary,
            'issues' => $issues,
        ];
    }

    protected function runVulnerabilityScan(bool $showProgress): array
    {
        $scanner = new VulnerabilityScanner();
        $items = $this->getVulnerabilityItems();
        $total = count($items);
        $progress = $showProgress && $total > 0
            ? Utils\make_progress_bar('Scanning installed components for vulnerabilities', $total)
            : null;

        $summary = [
            'scanned_targets' => $total,
            'processed' => 0,
            'vulnerable_targets' => 0,
            'total_findings' => 0,
            'critical' => 0,
            'high' => 0,
            'medium' => 0,
            'low' => 0,
            'unknown' => 0,
            'errors' => 0,
        ];
        $issues = [];

        foreach ($items as $item) {
            $scanResult = $scanner->scanItem($item);
            $componentName = $this->componentDisplayName($item);

            if (!($scanResult['success'] ?? false)) {
                $summary['errors']++;
                $issues[] = [
                    'component' => $componentName,
                    'type' => $item['type'],
                    'version' => (string) ($item['version'] ?? ''),
                    'severity' => 'error',
                    'findings' => 0,
                    'cves' => '',
                    'error' => (string) ($scanResult['message'] ?? 'Unknown error'),
                    'details' => [],
                ];
            } else {
                $vulnerabilities = isset($scanResult['vulnerabilities']) && is_array($scanResult['vulnerabilities'])
                    ? array_values($scanResult['vulnerabilities'])
                    : [];

                if (!empty($vulnerabilities)) {
                    $summary['vulnerable_targets']++;
                    $summary['total_findings'] += count($vulnerabilities);

                    foreach ($vulnerabilities as $vulnerability) {
                        $severity = $this->normalizeVulnerabilitySeverity($vulnerability['cvss_rating'] ?? '');
                        $summary[$severity]++;
                    }

                    $issues[] = [
                        'component' => $componentName,
                        'type' => (string) ($item['type'] ?? ''),
                        'version' => (string) ($item['version'] ?? ''),
                        'severity' => $this->highestVulnerabilitySeverity($vulnerabilities),
                        'findings' => count($vulnerabilities),
                        'cves' => $this->collectVulnerabilityCves($vulnerabilities),
                        'error' => '',
                        'details' => $vulnerabilities,
                    ];
                }
            }

            $summary['processed']++;

            if ($progress) {
                $progress->tick();
            }
        }

        if ($progress) {
            $progress->finish();
        }

        return [
            'summary' => $summary,
            'issues' => $issues,
        ];
    }

    protected function renderMalwareResult(array $result, string $format): void
    {
        if ($format === 'json') {
            $this->outputJson($result);
            return;
        }

        WP_CLI::line('');
        WP_CLI::line('Summary');
        WP_CLI::line('-------');
        Utils\format_items('table', [$result['summary']], array_keys($result['summary']));

        WP_CLI::line('');
        WP_CLI::line('Findings');
        WP_CLI::line('--------');

        if (empty($result['issues'])) {
            WP_CLI::line('No malware findings detected.');
            return;
        }

        Utils\format_items('table', $result['issues'], ['file', 'status', 'severity', 'findings', 'patterns', 'error']);
    }

    protected function renderIntegrityResult(array $result, string $format): void
    {
        if ($format === 'json') {
            $this->outputJson($result);
            return;
        }

        WP_CLI::line('');
        WP_CLI::line('Summary');
        WP_CLI::line('-------');
        Utils\format_items('table', [$result['summary']], array_keys($result['summary']));

        WP_CLI::line('');
        WP_CLI::line('Issues');
        WP_CLI::line('------');

        if (empty($result['issues'])) {
            WP_CLI::line('No integrity issues detected.');
            return;
        }

        Utils\format_items('table', $result['issues'], ['file_path', 'status', 'is_core', 'error']);
    }

    protected function renderVulnerabilityResult(array $result, string $format): void
    {
        if ($format === 'json') {
            $this->outputJson($result);
            return;
        }

        WP_CLI::line('');
        WP_CLI::line('Summary');
        WP_CLI::line('-------');
        Utils\format_items('table', [$result['summary']], array_keys($result['summary']));

        WP_CLI::line('');
        WP_CLI::line('Findings');
        WP_CLI::line('--------');

        if (empty($result['issues'])) {
            WP_CLI::line('No vulnerable components detected.');
            return;
        }

        Utils\format_items('table', $result['issues'], ['component', 'type', 'version', 'severity', 'findings', 'cves', 'error']);
    }

    protected function hasMalwareIssues(array $result): bool
    {
        $summary = $result['summary'] ?? [];
        return ((int) ($summary['infected'] ?? 0) + (int) ($summary['error'] ?? 0)) > 0;
    }

    protected function hasIntegrityIssues(array $result): bool
    {
        $summary = $result['summary'] ?? [];
        return ((int) ($summary['modified'] ?? 0)
            + (int) ($summary['missing'] ?? 0)
            + (int) ($summary['unexpected'] ?? 0)
            + (int) ($summary['error'] ?? 0)
            + (int) ($summary['unknown'] ?? 0)) > 0;
    }

    protected function hasVulnerabilityIssues(array $result): bool
    {
        $summary = $result['summary'] ?? [];
        return ((int) ($summary['vulnerable_targets'] ?? 0) + (int) ($summary['errors'] ?? 0)) > 0;
    }

    protected function finishCommand(string $label, bool $hasIssues, bool $failOnFindings, bool $jsonOutput): void
    {
        if ($hasIssues) {
            if ($failOnFindings) {
                if ($jsonOutput) {
                    WP_CLI::halt(1);
                }

                WP_CLI::error($label . ' completed with findings.');
            }

            if (!$jsonOutput) {
                WP_CLI::warning($label . ' completed with findings.');
            }

            return;
        }

        if (!$jsonOutput) {
            WP_CLI::success($label . ' completed with no findings.');
        }
    }

    protected function outputJson(array $payload): void
    {
        WP_CLI::line(wp_json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
    }

    protected function getScope(array $assoc_args): string
    {
        $scope = isset($assoc_args['scope']) ? sanitize_key((string) $assoc_args['scope']) : 'all';
        $allowed = ['all', 'wp-content', 'plugins', 'themes'];

        if (!in_array($scope, $allowed, true)) {
            WP_CLI::error('Invalid scope. Allowed values: all, wp-content, plugins, themes.');
        }

        return $scope;
    }

    protected function getFormat(array $assoc_args): string
    {
        $format = isset($assoc_args['format']) ? sanitize_key((string) $assoc_args['format']) : 'table';

        if (!in_array($format, ['table', 'json'], true)) {
            WP_CLI::error('Invalid format. Allowed values: table, json.');
        }

        return $format;
    }

    protected function shouldFailOnFindings(array $assoc_args): bool
    {
        return Utils\get_flag_value($assoc_args, 'fail-on-findings', false);
    }

    protected function normalizeRelativePath(string $path): string
    {
        $base = realpath(ABSPATH);
        $normalized = str_replace(['\\', '//'], '/', $path);

        if ($base) {
            $normalized = str_replace(str_replace('\\', '/', $base), '', $normalized);
        }

        return ltrim($normalized, '/');
    }

    protected function highestMalwareSeverity(array $findings): string
    {
        $severityOrder = ['high' => 3, 'medium' => 2, 'low' => 1];
        $highest = 'low';

        foreach ($findings as $finding) {
            $severity = strtolower((string) ($finding['severity'] ?? 'low'));
            if (($severityOrder[$severity] ?? 0) > ($severityOrder[$highest] ?? 0)) {
                $highest = $severity;
            }
        }

        return $highest;
    }

    protected function collectMalwarePatterns(array $findings): string
    {
        $patterns = [];

        foreach ($findings as $finding) {
            if (!empty($finding['patterns']) && is_array($finding['patterns'])) {
                foreach ($finding['patterns'] as $pattern) {
                    $patterns[(string) $pattern] = true;
                }
            }
        }

        return implode(', ', array_keys($patterns));
    }

    protected function getVulnerabilityItems(): array
    {
        $items = [];

        if (!function_exists('get_plugins')) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }

        foreach (get_plugins() as $pluginFile => $pluginData) {
            $items[] = [
                'type' => 'plugin',
                'slug' => dirname($pluginFile),
                'version' => (string) ($pluginData['Version'] ?? ''),
                'label' => 'Plugin: ' . (string) ($pluginData['Name'] ?? $pluginFile),
            ];
        }

        foreach (wp_get_themes() as $slug => $theme) {
            $version = (string) $theme->get('Version');
            if (!$slug || !$version) {
                continue;
            }

            $items[] = [
                'type' => 'theme',
                'slug' => (string) $slug,
                'version' => $version,
                'label' => 'Theme: ' . (string) $theme->get('Name'),
            ];
        }

        $items[] = [
            'type' => 'core',
            'slug' => 'wordpress',
            'version' => (string) get_bloginfo('version'),
            'label' => 'WordPress Core',
        ];

        return $items;
    }

    protected function componentDisplayName(array $item): string
    {
        if (($item['type'] ?? '') === 'core') {
            return 'WordPress Core';
        }

        $label = trim((string) ($item['label'] ?? ''));
        if ($label !== '') {
            return trim((string) preg_replace('/^(Plugin|Theme):\s*/i', '', $label));
        }

        return (string) ($item['slug'] ?? 'Unknown');
    }

    protected function normalizeVulnerabilitySeverity($severity): string
    {
        $severity = strtolower((string) $severity);

        if (in_array($severity, ['critical', 'high', 'medium', 'low'], true)) {
            return $severity;
        }

        return 'unknown';
    }

    protected function highestVulnerabilitySeverity(array $vulnerabilities): string
    {
        $weights = [
            'critical' => 5,
            'high' => 4,
            'medium' => 3,
            'low' => 2,
            'unknown' => 1,
        ];
        $highest = 'unknown';

        foreach ($vulnerabilities as $vulnerability) {
            $severity = $this->normalizeVulnerabilitySeverity($vulnerability['cvss_rating'] ?? '');
            if (($weights[$severity] ?? 0) > ($weights[$highest] ?? 0)) {
                $highest = $severity;
            }
        }

        return $highest;
    }

    protected function collectVulnerabilityCves(array $vulnerabilities): string
    {
        $cves = [];

        foreach ($vulnerabilities as $vulnerability) {
            $cve = trim((string) ($vulnerability['cve'] ?? ''));
            if ($cve !== '') {
                $cves[$cve] = true;
            }
        }

        return implode(', ', array_keys($cves));
    }
}
