<?php

namespace WPSN\Modules\Diagnostics;

defined('ABSPATH') || exit;

/**
 * Builds a human-readable diff between two saved release smoke runs.
 */
final class ReleaseSmokeDiffBuilder
{
    /** @var array<string,int> */
    private const SEVERITY_WEIGHT = [
        'ok' => 0,
        'info' => 1,
        'warn' => 2,
        'critical' => 3,
    ];

    /**
     * @param array<string,mixed> $baseline
     * @param array<string,mixed> $candidate
     * @return array<string,mixed>
     */
    public function compare(array $baseline, array $candidate): array
    {
        $baselineChecks = isset($baseline['checks']) && is_array($baseline['checks']) ? $baseline['checks'] : [];
        $candidateChecks = isset($candidate['checks']) && is_array($candidate['checks']) ? $candidate['checks'] : [];

        $keys = array_values(array_unique(array_merge(array_keys($baselineChecks), array_keys($candidateChecks))));
        sort($keys);

        $checkDiffs = [];
        $regressions = 0;
        $improvements = 0;
        $changed = 0;
        $unchanged = 0;

        foreach ($keys as $key) {
            $diff = $this->compareCheck(
                (string) $key,
                isset($baselineChecks[$key]) && is_array($baselineChecks[$key]) ? $baselineChecks[$key] : [],
                isset($candidateChecks[$key]) && is_array($candidateChecks[$key]) ? $candidateChecks[$key] : []
            );

            $status = (string) ($diff['status'] ?? 'changed');
            if ($status === 'regressed') {
                $regressions++;
            } elseif ($status === 'improved') {
                $improvements++;
            } elseif ($status === 'unchanged') {
                $unchanged++;
            } else {
                $changed++;
            }

            $checkDiffs[$key] = $diff;
        }

        $baselineSeverity = $this->normalizeSeverity(isset($baseline['severity']) ? (string) $baseline['severity'] : 'info');
        $candidateSeverity = $this->normalizeSeverity(isset($candidate['severity']) ? (string) $candidate['severity'] : 'info');

        return [
            'generated_at_utc' => gmdate('c'),
            'baseline' => $this->runHeader($baseline),
            'candidate' => $this->runHeader($candidate),
            'summary' => [
                'baseline_severity' => $baselineSeverity,
                'candidate_severity' => $candidateSeverity,
                'severity_status' => $this->compareSeverityStatus($baselineSeverity, $candidateSeverity),
                'count_deltas' => $this->countDeltas($baseline, $candidate),
                'regressions' => $regressions,
                'improvements' => $improvements,
                'changed' => $changed,
                'unchanged' => $unchanged,
                'checks_total' => count($checkDiffs),
            ],
            'checks' => $checkDiffs,
        ];
    }

    /**
     * @param array<string,mixed> $run
     * @return array<string,mixed>
     */
    private function runHeader(array $run): array
    {
        $counts = isset($run['counts']) && is_array($run['counts']) ? $run['counts'] : [];

        return [
            'id' => isset($run['id']) ? (string) $run['id'] : '',
            'created_at_utc' => isset($run['created_at_utc']) ? (string) $run['created_at_utc'] : '',
            'severity' => $this->normalizeSeverity(isset($run['severity']) ? (string) $run['severity'] : 'info'),
            'plugin_version' => isset($run['plugin_version']) ? (string) $run['plugin_version'] : '',
            'counts' => [
                'ok' => (int) ($counts['ok'] ?? 0),
                'info' => (int) ($counts['info'] ?? 0),
                'warn' => (int) ($counts['warn'] ?? 0),
                'critical' => (int) ($counts['critical'] ?? 0),
            ],
        ];
    }

    /**
     * @param array<string,mixed> $baseline
     * @param array<string,mixed> $candidate
     * @return array<string,int>
     */
    private function countDeltas(array $baseline, array $candidate): array
    {
        $baseCounts = isset($baseline['counts']) && is_array($baseline['counts']) ? $baseline['counts'] : [];
        $candCounts = isset($candidate['counts']) && is_array($candidate['counts']) ? $candidate['counts'] : [];

        return [
            'ok' => (int) ($candCounts['ok'] ?? 0) - (int) ($baseCounts['ok'] ?? 0),
            'info' => (int) ($candCounts['info'] ?? 0) - (int) ($baseCounts['info'] ?? 0),
            'warn' => (int) ($candCounts['warn'] ?? 0) - (int) ($baseCounts['warn'] ?? 0),
            'critical' => (int) ($candCounts['critical'] ?? 0) - (int) ($baseCounts['critical'] ?? 0),
        ];
    }

    /**
     * @param array<string,mixed> $baseline
     * @param array<string,mixed> $candidate
     * @return array<string,mixed>
     */
    private function compareCheck(string $key, array $baseline, array $candidate): array
    {
        $label = $this->checkLabel($key);
        $basePresent = !empty($baseline);
        $candPresent = !empty($candidate);

        if (!$basePresent && $candPresent) {
            return [
                'key' => $key,
                'label' => $label,
                'status' => 'added',
                'baseline' => null,
                'candidate' => $this->checkHeader($candidate),
                'case_diffs' => [],
            ];
        }

        if ($basePresent && !$candPresent) {
            return [
                'key' => $key,
                'label' => $label,
                'status' => 'removed',
                'baseline' => $this->checkHeader($baseline),
                'candidate' => null,
                'case_diffs' => [],
            ];
        }

        $baseSeverity = $this->normalizeSeverity(isset($baseline['severity']) ? (string) $baseline['severity'] : 'info');
        $candidateSeverity = $this->normalizeSeverity(isset($candidate['severity']) ? (string) $candidate['severity'] : 'info');
        $status = $this->compareSeverityStatus($baseSeverity, $candidateSeverity);

        $baseSummary = trim((string) ($baseline['summary'] ?? ''));
        $candidateSummary = trim((string) ($candidate['summary'] ?? ''));
        if ($status === 'unchanged' && $baseSummary !== $candidateSummary) {
            $status = 'changed';
        }

        $caseDiffs = [];
        if ($key === 'frontend_matrix') {
            $caseDiffs = $this->compareFrontendCases($baseline, $candidate);
            foreach ($caseDiffs as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $rowStatus = (string) ($row['status'] ?? 'unchanged');
                if ($rowStatus !== 'unchanged') {
                    if ($status === 'unchanged') {
                        $status = 'changed';
                    }
                    if ($rowStatus === 'regressed') {
                        $status = 'regressed';
                        break;
                    }
                }
            }
        }

        return [
            'key' => $key,
            'label' => $label,
            'status' => $status,
            'baseline' => $this->checkHeader($baseline),
            'candidate' => $this->checkHeader($candidate),
            'case_diffs' => $caseDiffs,
        ];
    }

    /**
     * @param array<string,mixed> $check
     * @return array<string,mixed>
     */
    private function checkHeader(array $check): array
    {
        return [
            'severity' => $this->normalizeSeverity(isset($check['severity']) ? (string) $check['severity'] : 'info'),
            'summary' => isset($check['summary']) ? (string) $check['summary'] : '',
            'ok' => !empty($check['ok']),
        ];
    }

    /**
     * @param array<string,mixed> $baseline
     * @param array<string,mixed> $candidate
     * @return array<int,array<string,mixed>>
     */
    private function compareFrontendCases(array $baseline, array $candidate): array
    {
        $baseCases = isset($baseline['details']['cases']) && is_array($baseline['details']['cases']) ? $baseline['details']['cases'] : [];
        $candidateCases = isset($candidate['details']['cases']) && is_array($candidate['details']['cases']) ? $candidate['details']['cases'] : [];
        $keys = array_values(array_unique(array_merge(array_keys($baseCases), array_keys($candidateCases))));
        sort($keys);

        $out = [];
        foreach ($keys as $key) {
            $baseRow = isset($baseCases[$key]) && is_array($baseCases[$key]) ? $baseCases[$key] : [];
            $candidateRow = isset($candidateCases[$key]) && is_array($candidateCases[$key]) ? $candidateCases[$key] : [];
            $basePresent = !empty($baseRow);
            $candPresent = !empty($candidateRow);
            $status = 'unchanged';

            if (!$basePresent && $candPresent) {
                $status = 'added';
            } elseif ($basePresent && !$candPresent) {
                $status = 'removed';
            } else {
                $baseSeverity = $this->normalizeSeverity(isset($baseRow['severity']) ? (string) $baseRow['severity'] : 'info');
                $candidateSeverity = $this->normalizeSeverity(isset($candidateRow['severity']) ? (string) $candidateRow['severity'] : 'info');
                $status = $this->compareSeverityStatus($baseSeverity, $candidateSeverity);
                if ($status === 'unchanged' && trim((string) ($baseRow['summary'] ?? '')) !== trim((string) ($candidateRow['summary'] ?? ''))) {
                    $status = 'changed';
                }
            }

            $out[] = [
                'slot' => (string) $key,
                'label' => $this->slotLabel((string) $key),
                'status' => $status,
                'baseline' => $basePresent ? $this->checkHeader($baseRow) : null,
                'candidate' => $candPresent ? $this->checkHeader($candidateRow) : null,
            ];
        }

        return $out;
    }

    private function normalizeSeverity(string $severity): string
    {
        $severity = sanitize_key($severity);
        if (!isset(self::SEVERITY_WEIGHT[$severity])) {
            return 'info';
        }

        return $severity;
    }

    private function compareSeverityStatus(string $baselineSeverity, string $candidateSeverity): string
    {
        $baseWeight = self::SEVERITY_WEIGHT[$baselineSeverity] ?? 1;
        $candidateWeight = self::SEVERITY_WEIGHT[$candidateSeverity] ?? 1;

        if ($candidateWeight > $baseWeight) {
            return 'regressed';
        }
        if ($candidateWeight < $baseWeight) {
            return 'improved';
        }

        return 'unchanged';
    }

    private function checkLabel(string $key): string
    {
        $map = [
            'frontend_matrix' => __('Frontend matrix', 'wp-seo-next'),
            'robots_txt' => __('robots.txt', 'wp-seo-next'),
            'sitemap_index' => __('Sitemap index', 'wp-seo-next'),
            'queue_bulk_undo' => __('Queue / Bulk / Undo', 'wp-seo-next'),
            'revisions_rollback' => __('Revisions rollback', 'wp-seo-next'),
            'import_export_rollback' => __('Import/Export rollback', 'wp-seo-next'),
            'admin_lists_quick_edit' => __('Admin Lists Quick Edit', 'wp-seo-next'),
        ];

        return $map[$key] ?? $key;
    }

    private function slotLabel(string $slot): string
    {
        $map = [
            'home' => __('Home', 'wp-seo-next'),
            'single' => __('Single', 'wp-seo-next'),
            'term' => __('Term', 'wp-seo-next'),
            'product' => __('Product', 'wp-seo-next'),
        ];

        return $map[$slot] ?? ucfirst($slot);
    }
}
