"""
Empirical Challenge & Stress-Test Harness for FreeExile Level 1-100 Progression Curve.
Verifies mathematical purity, boundary ratios, monotonicity, death penalty tiers,
and SQLite persistence in data/game_design_matrix.db.
"""

from __future__ import annotations
import math
import sqlite3
import sys
from pathlib import Path
from typing import Dict, List, Tuple, Any

# Ensure project root is in sys.path
PROJECT_ROOT = Path(__file__).resolve().parents[3]
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

from server.world.level_progression_curve import (
    calculate_piecewise_exp_curve,
    LevelExpBenchmark,
)


class ProgressionCurveChallenger:
    def __init__(self, db_path: Path | None = None) -> None:
        self.db_path = db_path or (PROJECT_ROOT / "data" / "game_design_matrix.db")
        self.benchmarks = calculate_piecewise_exp_curve()
        self.findings: List[Dict[str, Any]] = []

    def record_finding(
        self,
        name: str,
        passed: bool,
        details: str,
        expected: str,
        actual: str,
        severity: str = "HIGH",
    ) -> None:
        self.findings.append({
            "name": name,
            "passed": passed,
            "details": details,
            "expected": expected,
            "actual": actual,
            "severity": severity if not passed else "NONE",
        })

    def challenge_monotonicity(self) -> None:
        """Challenge 1: Strict Monotonicity: cumulative_exp(L) > cumulative_exp(L-1)."""
        violations_cum = []
        violations_target = []
        violations_delta = []

        # From memory curve
        for lvl in range(2, 101):
            prev = self.benchmarks[lvl - 1]
            curr = self.benchmarks[lvl]

            if curr.cumulative_exp <= prev.cumulative_exp:
                violations_cum.append((lvl, prev.cumulative_exp, curr.cumulative_exp))
            if curr.target_exp <= prev.target_exp:
                violations_target.append((lvl, prev.target_exp, curr.target_exp))

        for lvl in range(2, 100):
            prev_delta = self.benchmarks[lvl - 1].exp_to_next_level
            curr_delta = self.benchmarks[lvl].exp_to_next_level
            if curr_delta <= prev_delta:
                violations_delta.append((lvl, prev_delta, curr_delta))

        # Check level 100 delta is 0
        l100_delta = self.benchmarks[100].exp_to_next_level

        passed = len(violations_cum) == 0 and len(violations_target) == 0 and l100_delta == 0
        details = (
            f"Evaluated 100 levels. Cumulative violations: {len(violations_cum)}. "
            f"Target violations: {len(violations_target)}. Delta violations (1-99): {len(violations_delta)}. "
            f"Level 100 exp_to_next_level = {l100_delta}."
        )
        self.record_finding(
            name="Strict Monotonicity (Memory Curve)",
            passed=passed,
            details=details,
            expected="cumulative_exp(L) > cumulative_exp(L-1) for all L in [2, 100]",
            actual="PASS: 0 violations" if passed else f"FAIL: {violations_cum}",
            severity="CRITICAL",
        )

    def challenge_level_1_20_ratio(self) -> None:
        """Challenge 2: Level 1-20 cumulative EXP ratio: must be < 0.1% of lifetime EXP."""
        lifetime_exp = self.benchmarks[100].cumulative_exp
        cum_20 = self.benchmarks[20].cumulative_exp
        # Sum of deltas 1..20 is cumulative_exp at level 21
        cum_21 = self.benchmarks[21].cumulative_exp

        ratio_20 = cum_20 / lifetime_exp
        ratio_21 = cum_21 / lifetime_exp

        passed = ratio_20 < 0.001 and ratio_21 < 0.001
        details = (
            f"Level 20 cumulative: {cum_20:,} ({ratio_20:.6%}). "
            f"Level 21 cumulative (sum deltas 1..20): {cum_21:,} ({ratio_21:.6%}). "
            f"Lifetime EXP: {lifetime_exp:,}."
        )
        self.record_finding(
            name="Level 1-20 Cumulative EXP Ratio (< 0.1%)",
            passed=passed,
            details=details,
            expected="Ratio < 0.1% (< 0.001)",
            actual=f"L20={ratio_20:.6%}, L21={ratio_21:.6%}",
            severity="HIGH",
        )

    def challenge_level_99_100_delta_ratio(self) -> None:
        """Challenge 3: Level 99->100 delta EXP ratio:
        - must be >= 30% of cumulative 1-98 EXP
        - must be 25-35% of lifetime EXP.
        """
        cum_1_98 = self.benchmarks[99].cumulative_exp
        d99 = self.benchmarks[99].exp_to_next_level
        lifetime_exp = self.benchmarks[100].cumulative_exp

        ratio_vs_98 = d99 / cum_1_98
        ratio_vs_lifetime = d99 / lifetime_exp

        meets_30_percent_of_98 = ratio_vs_98 >= 0.30
        meets_25_35_percent_of_lifetime = 0.25 <= ratio_vs_lifetime <= 0.35

        passed = meets_30_percent_of_98 and meets_25_35_percent_of_lifetime
        details = (
            f"Cumulative 1-98 EXP: {cum_1_98:,}. "
            f"Delta 99->100: {d99:,}. "
            f"Lifetime EXP: {lifetime_exp:,}. "
            f"Delta / Cum(1-98) = {ratio_vs_98:.6%} (Req: >= 30.0%). "
            f"Delta / Lifetime = {ratio_vs_lifetime:.6%} (Req: 25.0% - 35.0%)."
        )

        expected = "ratio_vs_98 >= 30.0% AND 25.0% <= ratio_vs_lifetime <= 35.0%"
        actual = (
            f"ratio_vs_98 = {ratio_vs_98:.6%}, "
            f"ratio_vs_lifetime = {ratio_vs_lifetime:.6%} "
            f"({'DEFICIENT (< 25.0%)' if ratio_vs_lifetime < 0.25 else 'IN_RANGE'})"
        )

        self.record_finding(
            name="Level 99->100 Delta EXP Ratio",
            passed=passed,
            details=details,
            expected=expected,
            actual=actual,
            severity="HIGH" if not passed else "NONE",
        )

    def challenge_death_penalty_ratios(self) -> None:
        """Challenge 4: Death penalty ratios: verify exact values:
        1-60: 0.0, 61-80: 0.05, 81-89: 0.10, 90-98: 0.15, 99: 0.25, 100: 0.0.
        """
        mismatches = []
        for lvl in range(1, 101):
            actual_dp = self.benchmarks[lvl].death_penalty_ratio
            if lvl <= 60:
                expected_dp = 0.0
            elif lvl <= 80:
                expected_dp = 0.05
            elif lvl <= 89:
                expected_dp = 0.10
            elif lvl <= 98:
                expected_dp = 0.15
            elif lvl == 99:
                expected_dp = 0.25
            else:
                expected_dp = 0.0

            if abs(actual_dp - expected_dp) > 1e-6:
                mismatches.append((lvl, actual_dp, expected_dp))

        passed = len(mismatches) == 0
        details = (
            f"Checked 100 level tiers. Tiers: 1-60 (0%), 61-80 (5%), 81-89 (10%), "
            f"90-98 (15%), 99 (25%), 100 (0%). Mismatches: {len(mismatches)}."
        )
        self.record_finding(
            name="Death Penalty Tier Ratios (Memory Curve)",
            passed=passed,
            details=details,
            expected="Exact matching ratios across all 100 levels",
            actual="PASS: 0 mismatches" if passed else f"FAIL: {mismatches}",
            severity="CRITICAL",
        )

    def challenge_level_gap_constants(self) -> None:
        """Challenge 5: Level gap penalty constants: safe_range = 5, penalty_exp = 0.60, benchmark_exp = 25."""
        mismatches = []
        for lvl in range(1, 101):
            b = self.benchmarks[lvl]
            if b.level_gap_safe_range != 5:
                mismatches.append((lvl, "safe_range", b.level_gap_safe_range, 5))
            if abs(b.level_gap_penalty_exp - 0.60) > 1e-6:
                mismatches.append((lvl, "penalty_exp", b.level_gap_penalty_exp, 0.60))
            if b.monster_benchmark_exp != 25:
                mismatches.append((lvl, "benchmark_exp", b.monster_benchmark_exp, 25))

        passed = len(mismatches) == 0
        details = f"Checked 100 levels. Constant mismatches: {len(mismatches)}."
        self.record_finding(
            name="Level Gap Penalty Constants (Memory Curve)",
            passed=passed,
            details=details,
            expected="safe_range=5, penalty_exp=0.60, benchmark_exp=25",
            actual="PASS: 0 mismatches" if passed else f"FAIL: {mismatches}",
            severity="HIGH",
        )

    def challenge_sqlite_database_integrity(self) -> None:
        """Challenge 6: Stress-test SQLite table progression_benchmarks in data/game_design_matrix.db."""
        if not self.db_path.exists():
            self.record_finding(
                name="SQLite DB Existence",
                passed=False,
                details=f"DB file {self.db_path} does not exist",
                expected="DB file exists",
                actual="File missing",
                severity="CRITICAL",
            )
            return

        conn = sqlite3.connect(str(self.db_path))
        conn.row_factory = sqlite3.Row
        cur = conn.cursor()

        # Check columns
        cur.execute("PRAGMA table_info(progression_benchmarks)")
        cols = {r["name"]: r["type"] for r in cur.fetchall()}
        required_cols = [
            "level", "target_exp", "exp_to_next_level", "cumulative_exp",
            "player_base_hp", "player_benchmark_dps", "monster_base_hp",
            "monster_base_dps", "max_affix_tier_allowed", "death_penalty_ratio",
            "level_gap_safe_range", "level_gap_penalty_exp", "monster_benchmark_exp"
        ]
        missing_cols = [c for c in required_cols if c not in cols]

        # Check row count
        cur.execute("SELECT COUNT(*) FROM progression_benchmarks")
        row_count = cur.fetchone()[0]

        # Check values match memory benchmarks
        cur.execute("SELECT * FROM progression_benchmarks ORDER BY level ASC")
        db_rows = cur.fetchall()

        drift_errors = []
        for r in db_rows:
            lvl = r["level"]
            mem = self.benchmarks[lvl]
            if r["cumulative_exp"] != mem.cumulative_exp:
                drift_errors.append((lvl, "cumulative_exp", r["cumulative_exp"], mem.cumulative_exp))
            if r["exp_to_next_level"] != mem.exp_to_next_level:
                drift_errors.append((lvl, "exp_to_next_level", r["exp_to_next_level"], mem.exp_to_next_level))
            if abs(r["death_penalty_ratio"] - mem.death_penalty_ratio) > 1e-6:
                drift_errors.append((lvl, "death_penalty_ratio", r["death_penalty_ratio"], mem.death_penalty_ratio))
            if r["level_gap_safe_range"] != mem.level_gap_safe_range:
                drift_errors.append((lvl, "level_gap_safe_range", r["level_gap_safe_range"], mem.level_gap_safe_range))
            if abs(r["level_gap_penalty_exp"] - mem.level_gap_penalty_exp) > 1e-6:
                drift_errors.append((lvl, "level_gap_penalty_exp", r["level_gap_penalty_exp"], mem.level_gap_penalty_exp))
            if r["monster_benchmark_exp"] != mem.monster_benchmark_exp:
                drift_errors.append((lvl, "monster_benchmark_exp", r["monster_benchmark_exp"], mem.monster_benchmark_exp))

        conn.close()

        passed = len(missing_cols) == 0 and row_count == 100 and len(drift_errors) == 0
        details = (
            f"Total columns: {len(cols)}/13 (Missing: {missing_cols}). "
            f"Row count: {row_count}/100. Drift errors vs memory curve: {len(drift_errors)}."
        )
        self.record_finding(
            name="SQLite DB Persistence & Zero Drift",
            passed=passed,
            details=details,
            expected="13 columns, 100 rows, 0 drift errors",
            actual="PASS: 0 drift errors" if passed else f"FAIL: {drift_errors[:5]}",
            severity="CRITICAL",
        )

    def challenge_integer_overflow_and_precision(self) -> None:
        """Challenge 7: 64-bit integer headroom and float stability."""
        lifetime_exp = self.benchmarks[100].cumulative_exp
        int32_max = 2**31 - 1
        int64_max = 2**63 - 1

        exceeds_int32 = lifetime_exp > int32_max
        within_int64 = lifetime_exp < int64_max

        # Check precision: float64 has 53 bits of mantissa (~9e15), lifetime is ~2.4e10.
        float_safe = lifetime_exp < (2**53)

        details = (
            f"Lifetime EXP = {lifetime_exp:,}. Exceeds INT32 ({int32_max:,}): {exceeds_int32}. "
            f"Fits in INT64: {within_int64}. Float53 precision safe (< 9.0e15): {float_safe}."
        )
        passed = exceeds_int32 and within_int64 and float_safe
        self.record_finding(
            name="Integer Bounds & Headroom",
            passed=passed,
            details=details,
            expected="Requires 64-bit storage, safe in SQLite INTEGER (signed 64-bit) & IEEE 754 float",
            actual=f"PASS: Lifetime={lifetime_exp} fits safely in signed INT64",
            severity="MEDIUM",
        )

    def run_all(self) -> bool:
        self.challenge_monotonicity()
        self.challenge_level_1_20_ratio()
        self.challenge_level_99_100_delta_ratio()
        self.challenge_death_penalty_ratios()
        self.challenge_level_gap_constants()
        self.challenge_sqlite_database_integrity()
        self.challenge_integer_overflow_and_precision()

        all_passed = all(f["passed"] for f in self.findings)
        return all_passed

    def print_report(self) -> None:
        print("=" * 80)
        print("EMPIRICAL CHALLENGER REPORT: LEVEL PROGRESSION EXP CURVE")
        print("=" * 80)
        for idx, f in enumerate(self.findings, 1):
            status = "PASS [OK]" if f["passed"] else f"FAIL [{f['severity']}]"
            print(f"\n{idx}. {f['name']} -> {status}")
            print(f"   Details:  {f['details']}")
            print(f"   Expected: {f['expected']}")
            print(f"   Actual:   {f['actual']}")
        print("\n" + "=" * 80)
        overall = "APPROVED" if all(f["passed"] for f in self.findings) else "CHANGES REQUESTED"
        print(f"OVERALL EMPIRICAL VERDICT: {overall}")
        print("=" * 80)


if __name__ == "__main__":
    challenger = ProgressionCurveChallenger()
    success = challenger.run_all()
    challenger.print_report()
    sys.exit(0 if success else 1)
