<?php

namespace App\Support\Verification;

use DateTimeImmutable;

final class VerificationReportSchema
{
    public const string CURRENT_SCHEMA_VERSION = '1.5.0';

    /**
     * @return array<string, mixed>|null
     */
    public static function normalizeReport(mixed $report): ?array
    {
        if (! is_array($report)) {
            return null;
        }

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

        return $report;
    }

    /**
     * @param  array<string, mixed>  $report
     */
    public static function isValidReport(array $report): bool
    {
        $schemaVersion = self::schemaVersion($report);
        if ($schemaVersion === null || ! self::isSupportedSchemaVersion($schemaVersion)) {
            return false;
        }

        if (! self::isNonEmptyString($report['flow'] ?? null)) {
            return false;
        }

        if (! self::isIsoDateTimeString($report['generated_at'] ?? null)) {
            return false;
        }

        if (array_key_exists('identity', $report) && ! is_array($report['identity'])) {
            return false;
        }

        $summary = $report['summary'] ?? null;
        if (! is_array($summary)) {
            return false;
        }

        $overall = $summary['overall'] ?? null;
        if (! is_string($overall) || ! in_array($overall, VerificationReportOverall::values(), true)) {
            return false;
        }

        $counts = $summary['counts'] ?? null;
        if (! is_array($counts)) {
            return false;
        }

        foreach (['total', 'pass', 'fail', 'warn', 'skip', 'running'] as $key) {
            if (! self::isNonNegativeInt($counts[$key] ?? null)) {
                return false;
            }
        }

        $checks = $report['checks'] ?? null;
        if (! is_array($checks)) {
            return false;
        }

        foreach ($checks as $check) {
            if (! is_array($check) || ! self::isValidCheckResult($check)) {
                return false;
            }
        }

        if (array_key_exists('fingerprint', $report)) {
            $fingerprint = $report['fingerprint'];

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

        if (array_key_exists('previous_report_id', $report)) {
            $previousReportId = $report['previous_report_id'];

            if ($previousReportId !== null && ! is_int($previousReportId) && ! self::isNonEmptyString($previousReportId)) {
                return false;
            }
        }

        return true;
    }

    /**
     * @param  array<string, mixed>  $report
     */
    public static function schemaVersion(array $report): ?string
    {
        $candidate = $report['schema_version'] ?? null;

        if (! is_string($candidate)) {
            return null;
        }

        $candidate = trim($candidate);

        if ($candidate === '') {
            return null;
        }

        if (! preg_match('/^\d+\.\d+\.\d+$/', $candidate)) {
            return null;
        }

        return $candidate;
    }

    public static function isSupportedSchemaVersion(string $schemaVersion): bool
    {
        $parts = explode('.', $schemaVersion, 3);

        if (count($parts) !== 3) {
            return false;
        }

        $major = (int) $parts[0];

        return $major === 1;
    }

    /**
     * @param  array<string, mixed>  $check
     */
    private static function isValidCheckResult(array $check): bool
    {
        if (! self::isNonEmptyString($check['key'] ?? null)) {
            return false;
        }

        if (! self::isNonEmptyString($check['title'] ?? null)) {
            return false;
        }

        $status = $check['status'] ?? null;
        if (! is_string($status) || ! in_array($status, VerificationCheckStatus::values(), true)) {
            return false;
        }

        $severity = $check['severity'] ?? null;
        if (! is_string($severity)) {
            return false;
        }

        $severity = trim($severity);

        if ($severity !== '' && ! in_array($severity, VerificationCheckSeverity::values(), true)) {
            return false;
        }

        if (! is_bool($check['blocking'] ?? null)) {
            return false;
        }

        if (! self::isNonEmptyString($check['reason_code'] ?? null)) {
            return false;
        }

        if (! self::isNonEmptyString($check['message'] ?? null)) {
            return false;
        }

        $evidence = $check['evidence'] ?? null;
        if (! is_array($evidence)) {
            return false;
        }

        foreach ($evidence as $pointer) {
            if (! is_array($pointer) || ! self::isValidEvidencePointer($pointer)) {
                return false;
            }
        }

        $nextSteps = $check['next_steps'] ?? null;
        if (! is_array($nextSteps)) {
            return false;
        }

        foreach ($nextSteps as $step) {
            if (! is_array($step) || ! self::isValidNextStep($step)) {
                return false;
            }
        }

        return true;
    }

    /**
     * @param  array<string, mixed>  $pointer
     */
    private static function isValidEvidencePointer(array $pointer): bool
    {
        if (! self::isNonEmptyString($pointer['kind'] ?? null)) {
            return false;
        }

        $value = $pointer['value'] ?? null;

        return is_int($value) || self::isNonEmptyString($value);
    }

    /**
     * @param  array<string, mixed>  $step
     */
    private static function isValidNextStep(array $step): bool
    {
        if (! self::isNonEmptyString($step['label'] ?? null)) {
            return false;
        }

        if (! self::isNonEmptyString($step['url'] ?? null)) {
            return false;
        }

        return true;
    }

    private static function isNonEmptyString(mixed $value): bool
    {
        return is_string($value) && trim($value) !== '';
    }

    private static function isNonNegativeInt(mixed $value): bool
    {
        return is_int($value) && $value >= 0;
    }

    private static function isIsoDateTimeString(mixed $value): bool
    {
        if (! self::isNonEmptyString($value)) {
            return false;
        }

        try {
            new DateTimeImmutable((string) $value);

            return true;
        } catch (\Throwable) {
            return false;
        }
    }
}
