<?php
/**
 * WinRacing V48 — Chairman Explainable AI Engine
 * PHP 7.4 compatible.
 *
 * PRE-RACE ONLY:
 * - Reads V47 decision tables.
 * - Does not read racing_results.
 * - Does not use finish_position.
 *
 * Produces:
 * - Feature contribution breakdown
 * - Positive / negative reasons
 * - Confidence label
 * - Decision audit trail
 * - Race-level explanation summary
 */
class ChairmanExplainableAIEngineV48 {
    private $db;

    public function __construct(mysqli $db) {
        $this->db = $db;
        @$this->db->set_charset('utf8mb4');
    }

    private function q($s) {
        return "'" . $this->db->real_escape_string((string)$s) . "'";
    }

    private function n($v, $d=0) {
        return is_numeric($v) ? (float)$v : $d;
    }

    private function clamp($v, $min=0, $max=100) {
        return max($min, min($max, $v));
    }

    private function rows($sql) {
        $out = array();
        $rs = @$this->db->query($sql);
        if ($rs) {
            while ($r = $rs->fetch_assoc()) $out[] = $r;
        }
        return $out;
    }

    private function latestRunKey($raceDate, $venue='', $raceNum=0) {
        $w = array("race_date=" . $this->q($raceDate));
        if ($venue !== '') $w[] = "venue=" . $this->q($venue);
        if ((int)$raceNum > 0) $w[] = "race_num=" . (int)$raceNum;

        $rs = @$this->db->query(
            "SELECT run_key
             FROM wr_chairman_pre_race_run_log_v47
             WHERE ".implode(" AND ", $w)."
             ORDER BY created_at DESC, id DESC
             LIMIT 1"
        );
        if ($rs && ($row = $rs->fetch_assoc())) return (string)$row['run_key'];
        return '';
    }

    public function ensureTables() {
        $this->db->query("CREATE TABLE IF NOT EXISTS wr_chairman_explanation_v48 (
            id INT AUTO_INCREMENT PRIMARY KEY,
            explanation_key VARCHAR(700) NOT NULL,
            run_key VARCHAR(80) NOT NULL,
            source_run_key VARCHAR(80) NOT NULL,
            race_date DATE NULL,
            venue VARCHAR(80) NOT NULL DEFAULT '',
            race_num INT NOT NULL DEFAULT 0,
            horse_num VARCHAR(20) NOT NULL DEFAULT '',
            horse_name VARCHAR(180) NOT NULL DEFAULT '',
            pre_race_rank INT NOT NULL DEFAULT 0,
            ensemble_score DECIMAL(10,4) NOT NULL DEFAULT 0,
            explanation_grade VARCHAR(20) NOT NULL DEFAULT 'C',
            confidence_label VARCHAR(40) NOT NULL DEFAULT 'LOW',
            support_count INT NOT NULL DEFAULT 0,
            warning_count INT NOT NULL DEFAULT 0,
            contradiction_count INT NOT NULL DEFAULT 0,
            contribution_v44 DECIMAL(10,4) NOT NULL DEFAULT 0,
            contribution_p1 DECIMAL(10,4) NOT NULL DEFAULT 0,
            contribution_db7 DECIMAL(10,4) NOT NULL DEFAULT 0,
            contribution_signal DECIMAL(10,4) NOT NULL DEFAULT 0,
            contribution_bias DECIMAL(10,4) NOT NULL DEFAULT 0,
            contribution_confidence DECIMAL(10,4) NOT NULL DEFAULT 0,
            contribution_risk DECIMAL(10,4) NOT NULL DEFAULT 0,
            positive_reasons TEXT NULL,
            warning_reasons TEXT NULL,
            audit_summary TEXT NULL,
            explanation_json LONGTEXT NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            UNIQUE KEY uk_v48_explanation(explanation_key),
            KEY idx_v48_race(race_date,venue,race_num),
            KEY idx_v48_run(run_key),
            KEY idx_v48_score(ensemble_score)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

        $this->db->query("CREATE TABLE IF NOT EXISTS wr_chairman_race_explanation_v48 (
            id INT AUTO_INCREMENT PRIMARY KEY,
            summary_key VARCHAR(700) NOT NULL,
            run_key VARCHAR(80) NOT NULL,
            source_run_key VARCHAR(80) NOT NULL,
            race_date DATE NULL,
            venue VARCHAR(80) NOT NULL DEFAULT '',
            race_num INT NOT NULL DEFAULT 0,
            banker_horse VARCHAR(20) NOT NULL DEFAULT '',
            banker_score DECIMAL(10,4) NOT NULL DEFAULT 0,
            race_confidence DECIMAL(10,4) NOT NULL DEFAULT 0,
            race_clarity VARCHAR(40) NOT NULL DEFAULT 'LOW',
            support_summary TEXT NULL,
            warning_summary TEXT NULL,
            race_explanation TEXT NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            UNIQUE KEY uk_v48_race_summary(summary_key),
            KEY idx_v48_summary_race(race_date,venue,race_num),
            KEY idx_v48_summary_run(run_key)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

        $this->db->query("CREATE TABLE IF NOT EXISTS wr_chairman_explain_run_log_v48 (
            id INT AUTO_INCREMENT PRIMARY KEY,
            run_key VARCHAR(80) NOT NULL,
            source_run_key VARCHAR(80) NOT NULL,
            race_date DATE NULL,
            venue VARCHAR(80) NOT NULL DEFAULT '',
            status VARCHAR(30) NOT NULL DEFAULT 'DONE',
            explanation_rows INT NOT NULL DEFAULT 0,
            race_rows INT NOT NULL DEFAULT 0,
            note VARCHAR(255) NOT NULL DEFAULT 'PRE_RACE_XAI_ONLY',
            message TEXT NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            KEY idx_v48_log(run_key),
            KEY idx_v48_log_date(race_date,venue)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
    }

    private function confidenceLabel($score, $support, $warning, $contradiction) {
        $adjusted = $score + ($support * 2.2) - ($warning * 2.5) - ($contradiction * 3.0);
        if ($adjusted >= 76) return 'VERY_HIGH';
        if ($adjusted >= 66) return 'HIGH';
        if ($adjusted >= 55) return 'MEDIUM';
        if ($adjusted >= 44) return 'LOW';
        return 'VERY_LOW';
    }

    private function grade($score) {
        if ($score >= 78) return 'A+';
        if ($score >= 70) return 'A';
        if ($score >= 62) return 'B+';
        if ($score >= 54) return 'B';
        if ($score >= 46) return 'C';
        return 'D';
    }

    private function addReason(&$positive, &$warning, &$support, &$warnCount, $condition, $posText, $warnText='') {
        if ($condition) {
            $positive[] = $posText;
            $support++;
        } elseif ($warnText !== '') {
            $warning[] = $warnText;
            $warnCount++;
        }
    }

    private function explainRow($r) {
        // These weights mirror the V47 fusion formula.
        $cV44 = $this->n($r['v44_score']) * 0.38;
        $cP1 = $this->n($r['p1_score']) * 0.12;
        $cDb7 = $this->n($r['db7_score']) * 0.08;
        $cSignal = $this->n($r['signal_score']) * 0.06;
        $cBias = $this->n($r['bias_score']) * 0.02;
        $cConf = $this->n($r['confidence_score']) * 0.10;
        $cRisk = -($this->n($r['risk_score']) * 0.10);

        $positive = array();
        $warning = array();
        $support = 0;
        $warnCount = 0;
        $contradiction = 0;

        $this->addReason(
            $positive, $warning, $support, $warnCount,
            $this->n($r['v44_score']) >= 55,
            'V44 Production 分數具支持力',
            'V44 Production 分數偏低'
        );

        $this->addReason(
            $positive, $warning, $support, $warnCount,
            $this->n($r['p1_score']) >= 55,
            'P1／Matrix 賽前動力支持',
            'P1／Matrix 支持不足'
        );

        $this->addReason(
            $positive, $warning, $support, $warnCount,
            $this->n($r['db7_score']) >= 70,
            '7分表屬高分群',
            '7分表未達高分群'
        );

        $this->addReason(
            $positive, $warning, $support, $warnCount,
            $this->n($r['signal_score']) >= 55,
            'Signal／共振訊號同步',
            'Signal／共振訊號較弱'
        );

        $this->addReason(
            $positive, $warning, $support, $warnCount,
            $this->n($r['confidence_score']) >= 60,
            '模型信心達標',
            '模型信心未達60'
        );

        $this->addReason(
            $positive, $warning, $support, $warnCount,
            $this->n($r['risk_score']) < 55,
            '風險水平受控',
            '風險水平偏高'
        );

        if ((int)$r['banker_flag'] === 1) {
            $positive[] = 'V47 已列為賽前膽';
            $support++;
        }

        if ((int)$r['q_flag'] === 1) {
            $positive[] = 'V47 已列為 Q／六環 Top2';
            $support++;
        }

        if ((int)$r['cold_flag'] === 1) {
            $positive[] = '具冷門雷達價值';
            $support++;
        }

        if ((int)$r['avoid_flag'] === 1) {
            $warning[] = 'V47 已列為避開';
            $warnCount++;
        }

        // Detect contradictions between source rank and ensemble rank.
        if ((int)$r['source_rank'] <= 2 && (int)$r['pre_race_rank'] >= 5) {
            $warning[] = 'V44 高排名但 V47 融合後明顯下跌';
            $contradiction++;
        }

        if ((int)$r['source_rank'] >= 6 && (int)$r['pre_race_rank'] <= 3) {
            $positive[] = 'V47 發現隱藏提升：融合排名明顯高於 V44';
            $support++;
        }

        if ($this->n($r['db7_score']) >= 80 && $this->n($r['signal_score']) < 30) {
            $warning[] = '7分表高分，但即時 Signal 未同步';
            $contradiction++;
        }

        if ($this->n($r['signal_score']) >= 65 && $this->n($r['v44_score']) < 45) {
            $warning[] = 'Signal 強，但 V44 基礎分偏低';
            $contradiction++;
        }

        $confidence = $this->confidenceLabel(
            $this->n($r['ensemble_score']),
            $support,
            $warnCount,
            $contradiction
        );

        $audit = 'V48 XAI：#'.$r['horse_num'].' '.$r['horse_name'].
            '｜Final '.number_format($this->n($r['ensemble_score']),1).
            '｜支持 '.$support.
            '｜警告 '.$warnCount.
            '｜矛盾 '.$contradiction.
            '｜Confidence '.$confidence.
            '｜賽前資料解釋，不使用賽果。';

        return array(
            'contribution_v44' => round($cV44, 4),
            'contribution_p1' => round($cP1, 4),
            'contribution_db7' => round($cDb7, 4),
            'contribution_signal' => round($cSignal, 4),
            'contribution_bias' => round($cBias, 4),
            'contribution_confidence' => round($cConf, 4),
            'contribution_risk' => round($cRisk, 4),
            'positive' => $positive,
            'warning' => $warning,
            'support_count' => $support,
            'warning_count' => $warnCount,
            'contradiction_count' => $contradiction,
            'confidence_label' => $confidence,
            'grade' => $this->grade($this->n($r['ensemble_score'])),
            'audit_summary' => $audit
        );
    }

    public function build($raceDate, $venue='', $raceNum=0) {
        $this->ensureTables();

        $sourceRunKey = $this->latestRunKey($raceDate, $venue, $raceNum);
        if ($sourceRunKey === '') {
            return array(
                'status'=>'SOURCE_EMPTY',
                'pre_race_only'=>true,
                'results_used'=>false,
                'message'=>'V47 source run not found. Build V47 first.'
            );
        }

        $w = array(
            "race_date=".$this->q($raceDate),
            "run_key=".$this->q($sourceRunKey)
        );
        if ($venue !== '') $w[] = "venue=".$this->q($venue);
        if ((int)$raceNum > 0) $w[] = "race_num=".(int)$raceNum;

        $source = $this->rows(
            "SELECT *
             FROM wr_chairman_pre_race_decision_v47
             WHERE ".implode(" AND ", $w)."
             ORDER BY race_num, pre_race_rank"
        );

        $runKey = date('YmdHis').'_'.mt_rand(1000,9999);
        $byRace = array();
        $explanationCount = 0;
        $raceCount = 0;

        $this->db->begin_transaction();

        foreach ($source as $r) {
            $x = $this->explainRow($r);
            $rn = (int)$r['race_num'];

            if (!isset($byRace[$rn])) $byRace[$rn] = array();
            $byRace[$rn][] = array('row'=>$r, 'xai'=>$x);

            $key = sha1($runKey.'|V48|'.$raceDate.'|'.$venue.'|'.$rn.'|'.$r['horse_num']);

            $json = json_encode(array(
                'pre_race_only'=>true,
                'results_used'=>false,
                'feature_contributions'=>array(
                    'v44'=>$x['contribution_v44'],
                    'p1'=>$x['contribution_p1'],
                    'db7'=>$x['contribution_db7'],
                    'signal'=>$x['contribution_signal'],
                    'bias'=>$x['contribution_bias'],
                    'confidence'=>$x['contribution_confidence'],
                    'risk'=>$x['contribution_risk']
                ),
                'positive_reasons'=>$x['positive'],
                'warning_reasons'=>$x['warning']
            ), JSON_UNESCAPED_UNICODE);

            $sql = "INSERT INTO wr_chairman_explanation_v48
                (explanation_key,run_key,source_run_key,race_date,venue,race_num,horse_num,horse_name,
                 pre_race_rank,ensemble_score,explanation_grade,confidence_label,support_count,
                 warning_count,contradiction_count,contribution_v44,contribution_p1,contribution_db7,
                 contribution_signal,contribution_bias,contribution_confidence,contribution_risk,
                 positive_reasons,warning_reasons,audit_summary,explanation_json)
                VALUES (".
                $this->q($key).",".
                $this->q($runKey).",".
                $this->q($sourceRunKey).",".
                $this->q($raceDate).",".
                $this->q($venue).",".
                $rn.",".
                $this->q($r['horse_num']).",".
                $this->q($r['horse_name']).",".
                (int)$r['pre_race_rank'].",".
                $this->n($r['ensemble_score']).",".
                $this->q($x['grade']).",".
                $this->q($x['confidence_label']).",".
                (int)$x['support_count'].",".
                (int)$x['warning_count'].",".
                (int)$x['contradiction_count'].",".
                $x['contribution_v44'].",".
                $x['contribution_p1'].",".
                $x['contribution_db7'].",".
                $x['contribution_signal'].",".
                $x['contribution_bias'].",".
                $x['contribution_confidence'].",".
                $x['contribution_risk'].",".
                $this->q(implode('｜',$x['positive'])).",".
                $this->q(implode('｜',$x['warning'])).",".
                $this->q($x['audit_summary']).",".
                $this->q($json).")";
            $this->db->query($sql);
            $explanationCount++;
        }

        foreach ($byRace as $rn=>$items) {
            usort($items, function($a,$b){
                return $a['row']['pre_race_rank'] <=> $b['row']['pre_race_rank'];
            });

            $top = isset($items[0]) ? $items[0] : null;
            $second = isset($items[1]) ? $items[1] : null;

            $banker = $top ? (string)$top['row']['horse_num'] : '';
            $bankerScore = $top ? $this->n($top['row']['ensemble_score']) : 0;
            $secondScore = $second ? $this->n($second['row']['ensemble_score']) : 0;
            $gap = $bankerScore - $secondScore;

            $raceSupport = 0;
            $raceWarning = 0;
            $positiveSummary = array();
            $warningSummary = array();

            foreach (array_slice($items,0,4) as $item) {
                $raceSupport += $item['xai']['support_count'];
                $raceWarning += $item['xai']['warning_count'] + $item['xai']['contradiction_count'];
            }

            if ($top) {
                $positiveSummary = array_slice($top['xai']['positive'],0,4);
                $warningSummary = array_slice($top['xai']['warning'],0,4);
            }

            $raceConfidence = $this->clamp(
                $bankerScore*0.68 + $gap*4 + $raceSupport*0.9 - $raceWarning*1.2,
                0,100
            );

            $clarity = 'LOW';
            if ($raceConfidence >= 72 && $gap >= 5) $clarity = 'VERY_CLEAR';
            elseif ($raceConfidence >= 62) $clarity = 'CLEAR';
            elseif ($raceConfidence >= 50) $clarity = 'MIXED';

            $raceExplanation = 'V48 Race XAI：R'.$rn.
                '｜膽 #'.$banker.
                '｜Score '.number_format($bankerScore,1).
                '｜Gap '.number_format($gap,1).
                '｜Clarity '.$clarity.
                '｜支持 '.implode('；',$positiveSummary).
                ($warningSummary ? '｜警告 '.implode('；',$warningSummary) : '').
                '｜未使用賽果。';

            $summaryKey = sha1($runKey.'|V48RACE|'.$raceDate.'|'.$venue.'|'.$rn);

            $sql = "INSERT INTO wr_chairman_race_explanation_v48
                (summary_key,run_key,source_run_key,race_date,venue,race_num,banker_horse,
                 banker_score,race_confidence,race_clarity,support_summary,warning_summary,race_explanation)
                VALUES (".
                $this->q($summaryKey).",".
                $this->q($runKey).",".
                $this->q($sourceRunKey).",".
                $this->q($raceDate).",".
                $this->q($venue).",".
                (int)$rn.",".
                $this->q($banker).",".
                $bankerScore.",".
                $raceConfidence.",".
                $this->q($clarity).",".
                $this->q(implode('｜',$positiveSummary)).",".
                $this->q(implode('｜',$warningSummary)).",".
                $this->q($raceExplanation).")";
            $this->db->query($sql);
            $raceCount++;
        }

        $message = 'Built V48 XAI: explanations '.$explanationCount.
            ' | races '.$raceCount.
            ' | source V47 '.$sourceRunKey.
            ' | results not used';

        $this->db->query("INSERT INTO wr_chairman_explain_run_log_v48
            (run_key,source_run_key,race_date,venue,status,explanation_rows,race_rows,note,message)
            VALUES (".
            $this->q($runKey).",".
            $this->q($sourceRunKey).",".
            $this->q($raceDate).",".
            $this->q($venue).",'DONE',".
            $explanationCount.",".
            $raceCount.",'PRE_RACE_XAI_ONLY',".
            $this->q($message).")");

        $this->db->commit();

        return array(
            'status'=>'BUILT',
            'run_key'=>$runKey,
            'source_run_key'=>$sourceRunKey,
            'race_date'=>$raceDate,
            'venue'=>$venue,
            'race_num'=>(int)$raceNum,
            'pre_race_only'=>true,
            'results_used'=>false,
            'explanation_rows'=>$explanationCount,
            'race_rows'=>$raceCount,
            'message'=>$message
        );
    }

    public function latest($raceDate, $venue='', $raceNum=0) {
        $this->ensureTables();

        $w = array("race_date=".$this->q($raceDate));
        if ($venue !== '') $w[] = "venue=".$this->q($venue);
        if ((int)$raceNum > 0) $w[] = "race_num=".(int)$raceNum;
        $where = implode(" AND ", $w);

        $rs = @$this->db->query(
            "SELECT run_key
             FROM wr_chairman_explain_run_log_v48
             WHERE race_date=".$this->q($raceDate).
             ($venue!=='' ? " AND venue=".$this->q($venue) : "").
             " ORDER BY created_at DESC,id DESC LIMIT 1"
        );

        $runKey = '';
        if ($rs && ($row=$rs->fetch_assoc())) $runKey=(string)$row['run_key'];

        if ($runKey === '') {
            return array(
                'status'=>'EMPTY',
                'pre_race_only'=>true,
                'results_used'=>false,
                'race_summary'=>array(),
                'explanations'=>array()
            );
        }

        $raceSummary = $this->rows(
            "SELECT *
             FROM wr_chairman_race_explanation_v48
             WHERE {$where} AND run_key=".$this->q($runKey)."
             ORDER BY race_num"
        );

        $explanations = $this->rows(
            "SELECT *
             FROM wr_chairman_explanation_v48
             WHERE {$where} AND run_key=".$this->q($runKey)."
             ORDER BY race_num, pre_race_rank"
        );

        return array(
            'status'=>'OK',
            'run_key'=>$runKey,
            'pre_race_only'=>true,
            'results_used'=>false,
            'race_summary'=>$raceSummary,
            'explanations'=>$explanations
        );
    }
}
