<?php

declare(strict_types=1);

namespace App\Filament\Support;

use App\Models\OperationRun;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\RedactionIntegrity;
use App\Support\Verification\VerificationReportFingerprint;
use App\Support\Verification\VerificationReportSanitizer;
use App\Support\Verification\VerificationReportSchema;

final class VerificationReportViewer
{
    /**
     * @return array<string, mixed>|null
     */
    public static function report(OperationRun $run): ?array
    {
        $context = is_array($run->context) ? $run->context : [];
        $report = $context['verification_report'] ?? null;

        if (! is_array($report)) {
            return null;
        }

        $report = VerificationReportSanitizer::sanitizeReport($report);

        if (! VerificationReportSchema::isValidReport($report)) {
            return null;
        }

        return $report;
    }

    public static function previousReportId(array $report): ?int
    {
        $previousReportId = $report['previous_report_id'] ?? null;

        if (is_int($previousReportId) && $previousReportId > 0) {
            return $previousReportId;
        }

        if (is_string($previousReportId) && ctype_digit(trim($previousReportId))) {
            return (int) trim($previousReportId);
        }

        return null;
    }

    public static function fingerprint(array $report): ?string
    {
        $fingerprint = $report['fingerprint'] ?? null;

        if (is_string($fingerprint)) {
            $fingerprint = strtolower(trim($fingerprint));

            if (preg_match('/^[a-f0-9]{64}$/', $fingerprint)) {
                return $fingerprint;
            }
        }

        return VerificationReportFingerprint::forReport($report);
    }

    public static function previousRun(OperationRun $run, array $report): ?OperationRun
    {
        $previousReportId = self::previousReportId($report);

        if ($previousReportId === null) {
            return null;
        }

        $previous = OperationRun::query()
            ->whereKey($previousReportId)
            ->where('managed_environment_id', (int) $run->managed_environment_id)
            ->where('workspace_id', (int) $run->workspace_id)
            ->first();

        return $previous instanceof OperationRun ? $previous : null;
    }

    public static function shouldRenderForRun(OperationRun $run): bool
    {
        $context = is_array($run->context) ? $run->context : [];

        if (array_key_exists('verification_report', $context)) {
            return true;
        }

        return in_array((string) $run->type, ['provider.connection.check'], true);
    }

    /**
     * @param  array<string, array<string, mixed>>  $acknowledgements
     * @param  array{
     *     hostKind?: string,
     *     changeIndicator?: array{state: string, previous_report_id: int}|null,
     *     previousRunUrl?: string|null,
     *     nextStepPlacement?: 'shared_zone'|'host_action_zone',
     *     hostActions?: array<int, array{kind: string, label: string, ownedByHost: bool}>,
     *     hostVariation?: array{
     *         ownsNoRunState?: bool,
     *         ownsActiveState?: bool,
     *         supportsAssist?: bool,
     *         supportsAcknowledge?: bool,
     *         supportsTechnicalDetailsTrigger?: bool
     *     },
     *     optionalZones?: array<int, string>
     * }  $options
     * @return array<string, mixed>
     */
    public static function surface(OperationRun $run, array $acknowledgements = [], array $options = []): array
    {
        $report = self::report($run);
        $summary = is_array($report['summary'] ?? null) ? $report['summary'] : [];
        $counts = is_array($summary['counts'] ?? null) ? $summary['counts'] : [];
        $groupedChecks = self::groupedChecks($report, $acknowledgements);
        $changeIndicator = $options['changeIndicator'] ?? null;
        $hostKind = is_string($options['hostKind'] ?? null) && $options['hostKind'] !== ''
            ? (string) $options['hostKind']
            : 'operation_run_detail';
        $nextStepPlacement = ($options['nextStepPlacement'] ?? 'shared_zone') === 'host_action_zone'
            ? 'host_action_zone'
            : 'shared_zone';

        $hostActions = collect($options['hostActions'] ?? [])
            ->filter(static fn (mixed $action): bool => is_array($action))
            ->map(static function (array $action): array {
                $kind = is_string($action['kind'] ?? null) ? trim((string) $action['kind']) : 'navigation';
                $label = is_string($action['label'] ?? null) ? trim((string) $action['label']) : 'Action';

                return [
                    'kind' => $kind !== '' ? $kind : 'navigation',
                    'label' => $label !== '' ? $label : 'Action',
                    'ownedByHost' => (bool) ($action['ownedByHost'] ?? true),
                ];
            })
            ->values()
            ->all();

        $hostVariation = [
            'ownsNoRunState' => (bool) (($options['hostVariation']['ownsNoRunState'] ?? false)),
            'ownsActiveState' => (bool) (($options['hostVariation']['ownsActiveState'] ?? false)),
            'supportsAssist' => (bool) (($options['hostVariation']['supportsAssist'] ?? false)),
            'supportsAcknowledge' => (bool) (($options['hostVariation']['supportsAcknowledge'] ?? false)),
            'supportsTechnicalDetailsTrigger' => (bool) (($options['hostVariation']['supportsTechnicalDetailsTrigger'] ?? false)),
        ];

        $optionalZones = collect($options['optionalZones'] ?? ['technical_details', 'change_indicator', 'previous_run_context'])
            ->filter(static fn (mixed $zone): bool => is_string($zone) && trim($zone) !== '')
            ->map(static fn (string $zone): string => trim($zone))
            ->values()
            ->all();

        $overall = $summary['overall'] ?? null;
        $overallSpec = BadgeRenderer::spec(BadgeDomain::VerificationReportOverall, $overall);

        return [
            'hostKind' => $hostKind,
            'coreState' => $report === null ? 'unavailable' : 'completed',
            'summary' => [
                'overall' => $overall,
                'overallLabel' => $overallSpec->label,
                'counts' => [
                    'total' => (int) ($counts['total'] ?? 0),
                    'pass' => (int) ($counts['pass'] ?? 0),
                    'fail' => (int) ($counts['fail'] ?? 0),
                    'warn' => (int) ($counts['warn'] ?? 0),
                    'skip' => (int) ($counts['skip'] ?? 0),
                    'running' => (int) ($counts['running'] ?? 0),
                ],
                'changeIndicator' => is_array($changeIndicator) ? $changeIndicator : null,
            ],
            'issueGroups' => $groupedChecks['issueGroups'],
            'passedChecks' => $groupedChecks['passedChecks'],
            'diagnostics' => [
                'hasTechnicalZone' => true,
                'fingerprint' => is_array($report) ? self::fingerprint($report) : null,
                'previousRunUrl' => is_string($options['previousRunUrl'] ?? null) && $options['previousRunUrl'] !== ''
                    ? (string) $options['previousRunUrl']
                    : null,
                'operationRunId' => (int) $run->getKey(),
                'flow' => (string) $run->type,
                'completedAt' => $run->completed_at?->toJSON(),
            ],
            'viewZones' => [
                ['key' => 'issues', 'label' => 'Issues', 'defaultVisible' => true],
                ['key' => 'passed', 'label' => 'Passed', 'defaultVisible' => false],
            ],
            'nextSteps' => self::nextSteps($groupedChecks['issueGroups'], $nextStepPlacement),
            'hostActions' => $hostActions,
            'hostVariation' => $hostVariation,
            'optionalZones' => $optionalZones,
            'emptyState' => $report === null
                ? [
                    'title' => 'Verification report unavailable',
                    'message' => 'This operation doesn’t have a report yet. If it is still running, refresh in a moment. If it already completed, start verification again.',
                ]
                : null,
        ];
    }

    /**
     * @param  array<string, mixed>|null  $report
     * @param  array<string, array<string, mixed>>  $acknowledgements
     * @return array{issueGroups: array<int, array{label: string, checks: array<int, array<string, mixed>>, acknowledged?: bool}>, passedChecks: array<int, array<string, mixed>>}
     */
    private static function groupedChecks(?array $report, array $acknowledgements): array
    {
        $checks = is_array($report['checks'] ?? null) ? $report['checks'] : [];
        $ackByKey = [];

        foreach ($acknowledgements as $checkKey => $acknowledgement) {
            if (! is_string($checkKey) || $checkKey === '' || ! is_array($acknowledgement)) {
                continue;
            }

            $ackByKey[$checkKey] = $acknowledgement;
        }

        $blockers = [];
        $failures = [];
        $warnings = [];
        $acknowledgedIssues = [];
        $passed = [];

        foreach ($checks as $check) {
            if (! is_array($check)) {
                continue;
            }

            $key = is_string($check['key'] ?? null) ? trim((string) $check['key']) : '';

            if ($key === '') {
                continue;
            }

            $status = is_string($check['status'] ?? null) ? strtolower(trim((string) $check['status'])) : '';
            $blocking = (bool) ($check['blocking'] ?? false);
            $normalizedCheck = self::normalizeCheck($check, $ackByKey[$key] ?? null);

            if ($normalizedCheck['acknowledgement'] !== null) {
                $acknowledgedIssues[] = $normalizedCheck;

                continue;
            }

            if ($status === 'pass') {
                $passed[] = $normalizedCheck;

                continue;
            }

            if ($status === 'fail' && $blocking) {
                $blockers[] = $normalizedCheck;

                continue;
            }

            if ($status === 'fail') {
                $failures[] = $normalizedCheck;

                continue;
            }

            if ($status === 'warn') {
                $warnings[] = $normalizedCheck;
            }
        }

        $sortChecks = static fn (array $left, array $right): int => strcmp((string) ($left['key'] ?? ''), (string) ($right['key'] ?? ''));

        usort($blockers, $sortChecks);
        usort($failures, $sortChecks);
        usort($warnings, $sortChecks);
        usort($acknowledgedIssues, $sortChecks);
        usort($passed, $sortChecks);

        return [
            'issueGroups' => array_values(array_filter([
                ['label' => 'Blockers', 'checks' => $blockers],
                ['label' => 'Failures', 'checks' => $failures],
                ['label' => 'Warnings', 'checks' => $warnings],
                ['label' => 'Acknowledged issues', 'checks' => $acknowledgedIssues, 'acknowledged' => true],
            ], static fn (array $group): bool => ($group['checks'] ?? []) !== [])),
            'passedChecks' => $passed,
        ];
    }

    /**
     * @param  array<string, mixed>  $check
     * @param  array<string, mixed>|null  $acknowledgement
     * @return array<string, mixed>
     */
    private static function normalizeCheck(array $check, ?array $acknowledgement): array
    {
        $nextSteps = collect($check['next_steps'] ?? [])
            ->filter(static fn (mixed $step): bool => is_array($step))
            ->map(static function (array $step): array {
                $label = is_string($step['label'] ?? null) ? trim((string) $step['label']) : '';
                $url = is_string($step['url'] ?? null) ? trim((string) $step['url']) : '';

                return [
                    'label' => $label,
                    'url' => $url,
                ];
            })
            ->filter(static fn (array $step): bool => $step['label'] !== '' && $step['url'] !== '')
            ->values()
            ->all();

        return [
            'key' => is_string($check['key'] ?? null) ? trim((string) $check['key']) : '',
            'title' => is_string($check['title'] ?? null) && trim((string) $check['title']) !== ''
                ? trim((string) $check['title'])
                : 'Check',
            'message' => is_string($check['message'] ?? null) && trim((string) $check['message']) !== ''
                ? trim((string) $check['message'])
                : null,
            'status' => is_string($check['status'] ?? null) ? trim((string) $check['status']) : null,
            'severity' => is_string($check['severity'] ?? null) ? trim((string) $check['severity']) : null,
            'reason_code' => is_string($check['reason_code'] ?? null) ? trim((string) $check['reason_code']) : null,
            'blocking' => (bool) ($check['blocking'] ?? false),
            'next_steps' => $nextSteps,
            'acknowledgement' => is_array($acknowledgement) ? $acknowledgement : null,
        ];
    }

    /**
     * @param  array<int, array{label: string, checks: array<int, array<string, mixed>>, acknowledged?: bool}>  $issueGroups
     * @return array<int, array{label: string, placement: string, ownedByHost: bool, actionKind: string|null}>
     */
    private static function nextSteps(array $issueGroups, string $placement): array
    {
        $steps = [];

        foreach ($issueGroups as $group) {
            foreach ($group['checks'] as $check) {
                foreach ($check['next_steps'] ?? [] as $step) {
                    if (! is_array($step)) {
                        continue;
                    }

                    $label = is_string($step['label'] ?? null) ? trim((string) $step['label']) : '';

                    if ($label === '' || array_key_exists($label, $steps)) {
                        continue;
                    }

                    $steps[$label] = [
                        'label' => $label,
                        'placement' => $placement,
                        'ownedByHost' => $placement === 'host_action_zone',
                        'actionKind' => $placement === 'host_action_zone' ? 'assist' : 'navigation',
                    ];
                }
            }
        }

        return array_values($steps);
    }

    /**
     * @param  array<string, mixed>|null  $report
     * @return array<int, string>
     */
    public static function redactionNotes(?array $report): array
    {
        return RedactionIntegrity::verificationNotes($report);
    }
}
