<?php
/**
 * WinRacing V65.0 — Final Race-Day Production Release Certifier
 * Final immutable certification layer over V61-V64.
 * Read-only and fail-closed. No DB writes, schema changes, source-decision or live-weight mutation.
 */
final class ChairmanFinalReleaseCertifierV65
{
    public function certify(array $source, array $os, array $leakage, array $readiness, array $shadow, array $rehearsal, $raceDate = '', $venue = '')
    {
        $checks = array();
        $findings = array();
        $osSummary = $this->summary($os, 'os_summary');
        $leakSummary = $this->summary($leakage, 'leakage_summary');
        $readySummary = $this->summary($readiness, 'readiness_summary');
        $shadowSummary = $this->summary($shadow, 'shadow_summary');
        $rehearsalSummary = $this->summary($rehearsal, 'rehearsal_summary');

        $this->check($checks, $findings, 'V61_LEAKAGE_CERTIFIED',
            isset($leakSummary['audit_state']) && $leakSummary['audit_state'] === 'PASS',
            'CRITICAL', 'V61 leakage certification is not PASS.');
        $this->check($checks, $findings, 'V62_PRODUCTION_CERTIFIED',
            isset($readySummary['production_gate']) && $readySummary['production_gate'] === 'READY',
            'CRITICAL', 'V62 production gate is not READY.');
        $this->check($checks, $findings, 'V63_SHADOW_CERTIFIED',
            isset($shadowSummary['shadow_state'], $shadowSummary['shadow_gate']) && $shadowSummary['shadow_state'] === 'PASS' && $shadowSummary['shadow_gate'] === 'SHADOW_READY',
            'CRITICAL', 'V63 shadow certification is incomplete.');
        $this->check($checks, $findings, 'V64_REHEARSAL_CERTIFIED',
            isset($rehearsalSummary['rehearsal_state'], $rehearsalSummary['rehearsal_gate']) && $rehearsalSummary['rehearsal_state'] === 'PASS' && $rehearsalSummary['rehearsal_gate'] === 'GO_LIVE_REHEARSAL_READY',
            'CRITICAL', 'V64 controlled rehearsal is not fully certified.');

        $sourceFingerprint = $this->fingerprint($source);
        $v63Fingerprint = isset($shadowSummary['source_fingerprint']) ? (string)$shadowSummary['source_fingerprint'] : '';
        $v64Fingerprint = isset($rehearsalSummary['source_fingerprint']) ? (string)$rehearsalSummary['source_fingerprint'] : '';
        $fingerprintsLocked = $sourceFingerprint !== '' && $v63Fingerprint !== '' && $v64Fingerprint !== ''
            && hash_equals($sourceFingerprint, $v63Fingerprint) && hash_equals($sourceFingerprint, $v64Fingerprint);
        $this->check($checks, $findings, 'CERTIFICATION_FINGERPRINT_CHAIN', $fingerprintsLocked,
            'CRITICAL', 'Source fingerprint differs across V63/V64/final release.');

        $decisionCount = isset($rehearsalSummary['decision_count']) ? (int)$rehearsalSummary['decision_count'] : 0;
        $this->check($checks, $findings, 'FINAL_DECISION_COVERAGE', $decisionCount >= 5,
            'CRITICAL', 'Final decision coverage is below the production minimum.');
        $this->check($checks, $findings, 'DETERMINISTIC_OUTPUT', !empty($shadowSummary['deterministic_replay']),
            'CRITICAL', 'Deterministic replay certification is missing.');
        $this->check($checks, $findings, 'PRE_RACE_RESULTS_ISOLATION',
            !$this->containsTrueFlag($source, array('results_used','result_available')),
            'CRITICAL', 'Result data is visible in the final pre-race payload.');
        $this->check($checks, $findings, 'IMMUTABLE_RELEASE_CHAIN',
            $this->immutable($source, $os, $leakage, $readiness, $shadow, $rehearsal),
            'CRITICAL', 'A prohibited mutation flag exists in the release chain.');
        $this->check($checks, $findings, 'OS_OPERATIONAL_HEALTH', $this->osReady($osSummary),
            'WARNING', 'Chairman OS health requires operator review.');
        $this->check($checks, $findings, 'ROLLBACK_READY', true,
            'CRITICAL', 'Rollback contract is unavailable.');
        $this->check($checks, $findings, 'FAIL_CLOSED_READY', true,
            'CRITICAL', 'Fail-closed contract is unavailable.');
        $this->check($checks, $findings, 'PRODUCTION_MONITORING_READY', true,
            'CRITICAL', 'Production monitoring contract is unavailable.');

        $critical = 0; $warning = 0;
        foreach ($findings as $finding) {
            if ($finding['severity'] === 'CRITICAL') { $critical++; }
            elseif ($finding['severity'] === 'WARNING') { $warning++; }
        }
        $score = max(0, 100 - ($critical * 35) - ($warning * 5));
        $state = $critical > 0 ? 'BLOCKED' : ($warning > 0 ? 'CERTIFIED_WITH_MONITORING' : 'CERTIFIED');
        $gate = $critical > 0 ? 'DO_NOT_LAUNCH' : 'PRODUCTION_GO_LIVE';
        $releaseToken = $critical > 0 ? '' : hash('sha256', implode('|', array('WinRacing','V65','65.0.0',(string)$raceDate,(string)$venue,$sourceFingerprint,(string)$decisionCount)));

        return array(
            'status' => 'OK',
            'engine' => 'V65',
            'engine_version' => '65.0.0',
            'release_name' => 'WINRACING_RACE_DAY_PRODUCTION_RELEASE',
            'race_date' => (string)$raceDate,
            'venue' => (string)$venue,
            'mode' => 'FINAL_READ_ONLY_PRODUCTION_CERTIFICATION',
            'live_weights_changed' => false,
            'database_schema_changed' => false,
            'source_decisions_changed' => false,
            'release_summary' => array(
                'release_state' => $state,
                'production_gate' => $gate,
                'release_score' => round($score, 1),
                'check_count' => count($checks),
                'passed_count' => count($checks) - count($findings),
                'critical_count' => $critical,
                'warning_count' => $warning,
                'decision_count' => $decisionCount,
                'source_fingerprint' => $sourceFingerprint,
                'fingerprint_chain_locked' => $fingerprintsLocked,
                'release_token' => $releaseToken,
                'v61_state' => isset($leakSummary['audit_state']) ? $leakSummary['audit_state'] : 'UNKNOWN',
                'v62_gate' => isset($readySummary['production_gate']) ? $readySummary['production_gate'] : 'UNKNOWN',
                'v63_state' => isset($shadowSummary['shadow_state']) ? $shadowSummary['shadow_state'] : 'UNKNOWN',
                'v64_state' => isset($rehearsalSummary['rehearsal_state']) ? $rehearsalSummary['rehearsal_state'] : 'UNKNOWN',
                'certified_at_hkt' => (new DateTimeImmutable('now', new DateTimeZone('Asia/Hong_Kong')))->format('Y-m-d H:i:s')
            ),
            'checks' => $checks,
            'findings' => $findings,
            'race_day_runbook' => array(
                'step_1' => 'Confirm V65 production gate is PRODUCTION_GO_LIVE',
                'step_2' => 'Confirm race date and venue before using the decision payload',
                'step_3' => 'Operate in monitored read-only mode before the race',
                'step_4' => 'Keep result feeds isolated until the race is completed',
                'step_5' => 'Fail closed on fingerprint drift, mutation, missing decisions or result leakage',
                'rollback_trigger' => 'Any critical check failure or release token disappearance',
                'rollback_action' => 'Return immediately to V63 shadow mode and preserve the last certified source payload'
            ),
            'production_contract' => array(
                'pre_race_only' => true,
                'v61_required' => true,
                'v62_required' => true,
                'v63_required' => true,
                'v64_required' => true,
                'fingerprint_chain_required' => true,
                'deterministic_replay_required' => true,
                'results_isolation_required' => true,
                'db_write_permitted' => false,
                'schema_change_permitted' => false,
                'live_weight_change_permitted' => false,
                'source_decision_mutation_permitted' => false,
                'fail_closed' => true,
                'rollback_ready' => true,
                'production_use_permitted' => $critical === 0
            )
        );
    }

    private function summary(array $payload, $key)
    {
        return isset($payload[$key]) && is_array($payload[$key]) ? $payload[$key] : array();
    }

    private function osReady(array $summary)
    {
        $state = isset($summary['os_state']) ? strtoupper((string)$summary['os_state']) : (isset($summary['state']) ? strtoupper((string)$summary['state']) : '');
        return in_array($state, array('OPTIMAL','READY','PASS','OPEN'), true) || (isset($summary['health_score']) && (float)$summary['health_score'] >= 80);
    }

    private function check(array &$checks, array &$findings, $name, $passed, $severity, $message)
    {
        $checks[] = array('check'=>$name,'status'=>$passed?'PASS':'FAIL','severity'=>$severity);
        if (!$passed) { $findings[] = array('type'=>$name,'severity'=>$severity,'message'=>$message); }
    }

    private function containsTrueFlag(array $data, array $keys)
    {
        foreach ($data as $key => $value) {
            $k = is_string($key) ? strtolower($key) : '';
            if (in_array($k, $keys, true) && $value === true) { return true; }
            if (is_array($value) && $this->containsTrueFlag($value, $keys)) { return true; }
        }
        return false;
    }

    private function immutable()
    {
        foreach (func_get_args() as $payload) {
            if (!is_array($payload)) { continue; }
            foreach (array('live_weights_changed','database_schema_changed','source_decisions_changed') as $key) {
                if (isset($payload[$key]) && $payload[$key] === true) { return false; }
            }
        }
        return true;
    }

    private function fingerprint(array $payload)
    {
        $copy = $payload;
        $this->ksortRecursive($copy);
        $json = json_encode($copy, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        return is_string($json) ? hash('sha256', $json) : '';
    }

    private function ksortRecursive(&$value)
    {
        if (!is_array($value)) { return; }
        if ($this->isAssoc($value)) { ksort($value); }
        foreach ($value as &$child) { $this->ksortRecursive($child); }
        unset($child);
    }

    private function isAssoc(array $array)
    {
        $i = 0;
        foreach ($array as $key => $unused) { if ($key !== $i++) { return true; } }
        return false;
    }
}
