<?php

declare(strict_types=1);

namespace App\Support\Verification;

use App\Models\OperationRun;
use App\Support\OpsUx\RunFailureSanitizer;
use App\Support\Providers\ProviderReasonCodes;

final class VerificationReportWriter
{
    /**
     * @param  array<int, array<string, mixed>>  $checks
     * @param  array<string, mixed>  $identity
     * @return array<string, mixed>
     */
    public static function write(OperationRun $run, array $checks, array $identity = []): array
    {
        $flow = is_string($run->type) && trim($run->type) !== '' ? (string) $run->type : 'unknown';

        $report = self::build($flow, $checks, $identity);
        $report['previous_report_id'] = PreviousVerificationReportResolver::resolvePreviousReportId($run);

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

        if (! VerificationReportSchema::isValidReport($report)) {
            $report = VerificationReportSanitizer::sanitizeReport(self::buildFallbackReport($flow));
        }

        $context = is_array($run->context) ? $run->context : [];
        $context['verification_report'] = $report;

        $run->update(['context' => $context]);

        return $report;
    }

    /**
     * @param  array<int, array<string, mixed>>  $checks
     * @param  array<string, mixed>  $identity
     * @return array<string, mixed>
     */
    public static function build(string $flow, array $checks, array $identity = []): array
    {
        $flow = trim($flow);
        $flow = $flow !== '' ? $flow : 'unknown';

        $normalizedChecks = [];

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

            $normalizedChecks[] = self::normalizeCheckResult($check);
        }

        $counts = self::deriveCounts($normalizedChecks);

        $report = [
            'schema_version' => VerificationReportSchema::CURRENT_SCHEMA_VERSION,
            'flow' => $flow,
            'generated_at' => now()->toISOString(),
            'fingerprint' => VerificationReportFingerprint::forChecks($normalizedChecks),
            'previous_report_id' => null,
            'summary' => [
                'overall' => self::deriveOverall($normalizedChecks, $counts),
                'counts' => $counts,
            ],
            'checks' => $normalizedChecks,
        ];

        if ($identity !== []) {
            $report['identity'] = $identity;
        }

        return $report;
    }

    /**
     * @return array<string, mixed>
     */
    private static function buildFallbackReport(string $flow): array
    {
        return [
            'schema_version' => VerificationReportSchema::CURRENT_SCHEMA_VERSION,
            'flow' => $flow !== '' ? $flow : 'unknown',
            'generated_at' => now()->toISOString(),
            'fingerprint' => VerificationReportFingerprint::forChecks([]),
            'previous_report_id' => null,
            'summary' => [
                'overall' => VerificationReportOverall::NeedsAttention->value,
                'counts' => [
                    'total' => 0,
                    'pass' => 0,
                    'fail' => 0,
                    'warn' => 0,
                    'skip' => 0,
                    'running' => 0,
                ],
            ],
            'checks' => [],
        ];
    }

    /**
     * @param  array<string, mixed>  $check
     * @return array{
     *   key: string,
     *   title: string,
     *   status: string,
     *   severity: string,
     *   blocking: bool,
     *   reason_code: string,
     *   message: string,
     *   evidence: array<int, array{kind: string, value: int|string}>,
     *   next_steps: array<int, array{label: string, url: string}>
     * }
     */
    private static function normalizeCheckResult(array $check): array
    {
        $key = self::normalizeNonEmptyString($check['key'] ?? null, fallback: 'unknown_check');
        $title = self::normalizeNonEmptyString($check['title'] ?? null, fallback: 'Check');

        return [
            'key' => $key,
            'title' => $title,
            'status' => self::normalizeCheckStatus($check['status'] ?? null),
            'severity' => self::normalizeCheckSeverity($check['severity'] ?? null),
            'blocking' => is_bool($check['blocking'] ?? null) ? (bool) $check['blocking'] : false,
            'reason_code' => self::normalizeReasonCode($check['reason_code'] ?? null),
            'message' => self::normalizeNonEmptyString($check['message'] ?? null, fallback: '—'),
            'evidence' => self::normalizeEvidence($check['evidence'] ?? null),
            'next_steps' => self::normalizeNextSteps($check['next_steps'] ?? null),
        ];
    }

    private static function normalizeCheckStatus(mixed $status): string
    {
        if (! is_string($status)) {
            return VerificationCheckStatus::Fail->value;
        }

        $status = strtolower(trim($status));

        return in_array($status, VerificationCheckStatus::values(), true)
            ? $status
            : VerificationCheckStatus::Fail->value;
    }

    private static function normalizeCheckSeverity(mixed $severity): string
    {
        if (! is_string($severity)) {
            return '';
        }

        $severity = strtolower(trim($severity));

        return in_array($severity, VerificationCheckSeverity::values(), true) ? $severity : '';
    }

    private static function normalizeReasonCode(mixed $reasonCode): string
    {
        if (! is_string($reasonCode)) {
            return ProviderReasonCodes::UnknownError;
        }

        $reasonCode = strtolower(trim($reasonCode));

        if ($reasonCode === '') {
            return ProviderReasonCodes::UnknownError;
        }

        if (str_starts_with($reasonCode, 'ext.')) {
            return $reasonCode;
        }

        $reasonCode = RunFailureSanitizer::normalizeReasonCode($reasonCode);

        if (ProviderReasonCodes::isKnown($reasonCode)) {
            return $reasonCode;
        }

        return in_array($reasonCode, ['ok', 'not_applicable'], true)
            ? $reasonCode
            : ProviderReasonCodes::UnknownError;
    }

    /**
     * @return array<int, array{kind: string, value: int|string}>
     */
    private static function normalizeEvidence(mixed $evidence): array
    {
        if (! is_array($evidence)) {
            return [];
        }

        $normalized = [];

        foreach ($evidence as $pointer) {
            if (! is_array($pointer)) {
                continue;
            }

            $kind = self::normalizeNonEmptyString($pointer['kind'] ?? null, fallback: null);
            $value = $pointer['value'] ?? null;

            if ($kind === null) {
                continue;
            }

            if (! is_int($value) && ! is_string($value)) {
                continue;
            }

            if (is_string($value) && trim($value) === '') {
                continue;
            }

            $normalized[] = [
                'kind' => $kind,
                'value' => is_int($value) ? $value : trim($value),
            ];
        }

        return $normalized;
    }

    /**
     * @return array<int, array{label: string, url: string}>
     */
    private static function normalizeNextSteps(mixed $steps): array
    {
        return VerificationReportSanitizer::sanitizeNextStepsPayload($steps);
    }

    /**
     * @param  array<int, array{status: string, blocking: bool}>  $checks
     * @return array{total: int, pass: int, fail: int, warn: int, skip: int, running: int}
     */
    private static function deriveCounts(array $checks): array
    {
        $counts = [
            'total' => count($checks),
            'pass' => 0,
            'fail' => 0,
            'warn' => 0,
            'skip' => 0,
            'running' => 0,
        ];

        foreach ($checks as $check) {
            $status = $check['status'] ?? null;

            if (! is_string($status) || ! array_key_exists($status, $counts)) {
                continue;
            }

            $counts[$status] += 1;
        }

        return $counts;
    }

    /**
     * @param  array<int, array{status: string, blocking: bool}>  $checks
     * @param  array{total: int, pass: int, fail: int, warn: int, skip: int, running: int}  $counts
     */
    private static function deriveOverall(array $checks, array $counts): string
    {
        if (($counts['running'] ?? 0) > 0) {
            return VerificationReportOverall::Running->value;
        }

        if (($counts['total'] ?? 0) === 0) {
            return VerificationReportOverall::NeedsAttention->value;
        }

        foreach ($checks as $check) {
            if (($check['status'] ?? null) === VerificationCheckStatus::Fail->value && ($check['blocking'] ?? false) === true) {
                return VerificationReportOverall::Blocked->value;
            }
        }

        if (($counts['fail'] ?? 0) > 0 || ($counts['warn'] ?? 0) > 0) {
            return VerificationReportOverall::NeedsAttention->value;
        }

        return VerificationReportOverall::Ready->value;
    }

    private static function normalizeNonEmptyString(mixed $value, ?string $fallback): ?string
    {
        if (! is_string($value)) {
            return $fallback;
        }

        $value = trim($value);

        if ($value === '') {
            return $fallback;
        }

        return $value;
    }
}
