<?php
/**
 * WinRacing V61.0 — Pre-Race Data Leakage Auditor
 * Read-only / advisory-only. No DB writes, no schema changes, no live-weight changes.
 */
final class ChairmanPreRaceLeakageAuditorV61
{
    private $forbiddenKeys = array(
        'finish_order','result','results','race_result','racing_results','win_horse',
        'place_horse','third_horse','fourth_horse','dividend','payout','actual_outcome',
        'actual_rank','finish_position','winner','winning_horse','result_source'
    );

    private $allowedHistoricalContainers = array(
        'history','historical','historical_stats','past_results','form_history',
        'memory_records','chairman_experiences','pattern_memory'
    );

    public function audit(array $source, $raceDate = '', $venue = '')
    {
        $findings = array();
        $this->walk($source, '', $findings, false);

        $futureDateFindings = $this->scanDates($source, $raceDate);
        foreach ($futureDateFindings as $f) { $findings[] = $f; }

        $critical = 0; $warning = 0; $info = 0;
        foreach ($findings as $f) {
            if ($f['severity'] === 'CRITICAL') { $critical++; }
            elseif ($f['severity'] === 'WARNING') { $warning++; }
            else { $info++; }
        }

        $status = $critical > 0 ? 'BLOCK' : ($warning > 0 ? 'REVIEW' : 'PASS');
        $score = max(0, 100 - $critical * 30 - $warning * 8 - $info * 1);

        return array(
            'status' => 'OK',
            'engine' => 'V61',
            'engine_version' => '61.0.0',
            'race_date' => (string)$raceDate,
            'venue' => (string)$venue,
            'mode' => 'READ_ONLY_ADVISORY',
            'live_weights_changed' => false,
            'database_schema_changed' => false,
            'source_decisions_changed' => false,
            'leakage_summary' => array(
                'audit_state' => $status,
                'leakage_score' => round($score, 1),
                'finding_count' => count($findings),
                'critical_count' => $critical,
                'warning_count' => $warning,
                'info_count' => $info,
                'production_gate' => $critical > 0 ? 'CLOSED' : 'OPEN_WITH_REVIEW'
            ),
            'checks' => array(
                array('check'=>'FORBIDDEN_RESULT_FIELDS','status'=>$critical>0?'FAIL':'PASS'),
                array('check'=>'FUTURE_DATE_REFERENCE','status'=>count($futureDateFindings)>0?'FAIL':'PASS'),
                array('check'=>'RESULT_SOURCE_FLAG','status'=>$this->containsTrueResultFlag($source)?'FAIL':'PASS'),
                array('check'=>'IMMUTABLE_PRE_RACE_CONTRACT','status'=>'PASS')
            ),
            'findings' => $findings,
            'safety_contract' => array(
                'pre_race_only' => true,
                'results_must_be_unavailable' => true,
                'advisory_only' => true,
                'db_write_permitted' => false,
                'schema_change_permitted' => false
            )
        );
    }

    private function walk($value, $path, array &$findings, $insideHistorical)
    {
        if (!is_array($value)) { return; }
        foreach ($value as $key => $child) {
            $keyText = is_string($key) ? strtolower($key) : (string)$key;
            $newPath = $path === '' ? $keyText : $path . '.' . $keyText;
            $historical = $insideHistorical || in_array($keyText, $this->allowedHistoricalContainers, true);

            if (in_array($keyText, $this->forbiddenKeys, true)) {
                $isEmpty = $child === null || $child === '' || $child === array() || $child === false;
                if (!$isEmpty && !$historical) {
                    $severity = in_array($keyText, array('finish_order','actual_outcome','actual_rank','winner','winning_horse','win_horse'), true)
                        ? 'CRITICAL' : 'WARNING';
                    $findings[] = $this->finding(
                        'FORBIDDEN_FIELD_PRESENT',
                        $severity,
                        $newPath,
                        'Pre-race payload contains a result/outcome-related field with data.'
                    );
                }
            }

            if ($keyText === 'results_used' && $child === true) {
                $findings[] = $this->finding(
                    'RESULTS_USED_TRUE',
                    'CRITICAL',
                    $newPath,
                    'results_used=true violates the pre-race contract.'
                );
            }

            if (is_array($child)) {
                $this->walk($child, $newPath, $findings, $historical);
            }
        }
    }

    private function scanDates(array $source, $raceDate)
    {
        $out = array();
        if (!$raceDate || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $raceDate)) { return $out; }
        $target = strtotime($raceDate . ' 23:59:59');
        $this->walkDates($source, '', $target, $out);
        return $out;
    }

    private function walkDates($value, $path, $target, array &$out)
    {
        if (!is_array($value)) { return; }
        foreach ($value as $key => $child) {
            $keyText = is_string($key) ? strtolower($key) : (string)$key;
            $newPath = $path === '' ? $keyText : $path . '.' . $keyText;
            if (is_string($child) && preg_match('/^\d{4}-\d{2}-\d{2}/', $child)) {
                $ts = strtotime(substr($child,0,10) . ' 00:00:00');
                if ($ts !== false && $ts > $target) {
                    $out[] = $this->finding(
                        'FUTURE_DATE_REFERENCE',
                        'CRITICAL',
                        $newPath,
                        'Payload contains a date later than the selected race date: ' . substr($child,0,10)
                    );
                }
            }
            if (is_array($child)) { $this->walkDates($child, $newPath, $target, $out); }
        }
    }

    private function containsTrueResultFlag(array $source)
    {
        foreach ($source as $k => $v) {
            $key = is_string($k) ? strtolower($k) : '';
            if (($key === 'results_used' || $key === 'result_available') && $v === true) { return true; }
            if (is_array($v) && $this->containsTrueResultFlag($v)) { return true; }
        }
        return false;
    }

    private function finding($type, $severity, $path, $message)
    {
        return array(
            'type' => $type,
            'severity' => $severity,
            'path' => $path,
            'message' => $message
        );
    }
}
