"""
Comprehensive E2E Test Suite for FreeExile Level 1-100 Progression, Piecewise EXP Curve,
Hybrid Tiered Death Penalty & Godhood Avatar Metamorphosis (PoE2 2026 Standards).

4-Tier Methodology:
- Tier 1: Feature Coverage (F1-F8: EXP Curve, Schema, Level Gap, Death Penalty, Service, Trial 10, Godhood, Sim)
- Tier 2: Boundary & Corner Cases (Level boundaries, Gap thresholds, Safe floor, Resist caps)
- Tier 3: Cross-Feature Combinations (Progression -> Zone -> Combat -> Death -> Respawn)
- Tier 4: Real-World Scenarios (Full Journey Simulation, Level 99 Death Streak, Anti-Bot)
"""

import math
import os
import sqlite3
import sys
from pathlib import Path
from typing import Dict, List, Tuple, Any, Optional
import pytest

SERVER_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../server"))
if SERVER_DIR not in sys.path:
    sys.path.insert(0, SERVER_DIR)

from server.world.combat_engine import CombatEngine, CombatActor
from server.world.martial_matrix import FiveElements
from server.world.ascendancy_catalog import get_canonical_trials, get_ascendancy_nodes
from server.world.ascendancy_types import AscendancyAppliedEffects, AscendancyTrial

# --- Canonical Mathematical Oracles (Spec-Driven Ground Truth) ---

def calc_delta_exp(level: int) -> int:
    """7-segment piecewise exponential EXP curve delta for level -> level + 1."""
    if level < 1 or level >= 100:
        return 0
    if 1 <= level <= 20:
        return int(math.floor(500 * (level ** 2.3) + 100 * level))
    elif 21 <= level <= 40:
        d20 = calc_delta_exp(20)
        return int(math.floor(d20 * ((1 + 0.085 * (level - 20)) ** 2.4)))
    elif 41 <= level <= 60:
        d40 = calc_delta_exp(40)
        return int(math.floor(d40 * math.exp(0.092 * (level - 40))))
    elif 61 <= level <= 80:
        d60 = calc_delta_exp(60)
        return int(math.floor(d60 + 0.12 * d60 * (level - 60)))
    elif 81 <= level <= 90:
        d80 = calc_delta_exp(80)
        return int(math.floor(d80 * math.exp(0.145 * (level - 80))))
    elif 91 <= level <= 98:
        d90 = calc_delta_exp(90)
        return int(math.floor(d90 * math.exp(0.240 * (level - 90))))
    elif level == 99:
        sum_1_98 = sum(calc_delta_exp(i) for i in range(1, 99))
        return int(math.floor(0.35 * sum_1_98))
    return 0


def calc_level_gap_decay(player_level: int, monster_level: int) -> float:
    """Calculates level gap efficiency multiplier (>=10 levels lower gives <= 5%)."""
    gap = player_level - monster_level
    if abs(gap) <= 5:
        return 1.0
    if gap > 5:
        d = gap - 5
        return max(0.01, math.exp(-0.60 * d))
    d = (-gap) - 5
    return max(0.05, math.exp(-0.40 * d))


def calc_death_penalty_ratio(level: int) -> float:
    """Calculates tiered death penalty ratio (1-60: 0%, 61-80: 5%, 81-89: 10%, 90-99: 15%, 99->100: 25%)."""
    if level <= 60:
        return 0.0
    elif level <= 80:
        return 0.05
    elif level <= 89:
        return 0.10
    elif level <= 98:
        return 0.15
    elif level == 99:
        return 0.25
    return 0.0


# --- Fixtures ---

@pytest.fixture
def matrix_db_conn() -> sqlite3.Connection:
    db_path = Path("data/game_design_matrix.db")
    conn = sqlite3.connect(str(db_path))
    conn.row_factory = sqlite3.Row
    yield conn
    conn.close()


@pytest.fixture
def combat_pair() -> Tuple[CombatEngine, CombatActor, CombatActor]:
    engine = CombatEngine()
    attacker = CombatActor(1, "Boss Thần Ma", FiveElements.HOA, base_attack=200.0)
    defender = CombatActor(2, "Lưu Đày Giả", FiveElements.HOA, current_hp=1000.0, max_hp=1000.0)
    engine.register_actor(attacker)
    engine.register_actor(defender)
    return engine, attacker, defender


# --- Tier 1: Feature Coverage (F1 - F8) ---

class TestTier1FeatureCoverage:
    """Tier 1: Feature Coverage across F1 to F8 (>=5 test cases per feature)."""

    # --- F1: 7-Segment Piecewise EXP Curve ---
    def test_f01_exp_curve_monotonicity_all_levels(self) -> None:
        """EXP delta strictly increases from level 1 to 99."""
        deltas = [calc_delta_exp(lvl) for lvl in range(1, 100)]
        assert all(deltas[i] > deltas[i - 1] for i in range(1, len(deltas)))

    def test_f01_exp_curve_tutorial_tier1_budget(self) -> None:
        """Level 1-20 cumulative EXP is strictly < 0.1% of lifetime EXP."""
        total_1_20 = sum(calc_delta_exp(i) for i in range(1, 21))
        total_lifetime = sum(calc_delta_exp(i) for i in range(1, 100))
        assert (total_1_20 / total_lifetime) < 0.001

    def test_f01_exp_curve_story_deceleration_tiers2_3(self) -> None:
        """Segments 2 (21-40) and 3 (41-60) exhibit moderate deceleration."""
        d20, d40, d60 = calc_delta_exp(20), calc_delta_exp(40), calc_delta_exp(60)
        assert d40 > d20 * 2.0 and d60 > d40 * 3.0

    def test_f01_exp_curve_mapping_linear_and_steep_tiers4_6(self) -> None:
        """Segment 4 has stable linear growth; Segment 6 has steep exponential growth."""
        d60, d70, d80 = calc_delta_exp(60), calc_delta_exp(70), calc_delta_exp(80)
        assert (d70 - d60) == int(math.floor(0.12 * d60 * 10))
        d90, d95 = calc_delta_exp(90), calc_delta_exp(95)
        assert d95 > d90 * 3.0

    def test_f01_exp_curve_pinnacle_softwall_tier7_ratio(self) -> None:
        """Level 99->100 delta is >= 30% of total 1-99 EXP and 25-35% of lifetime EXP."""
        sum_1_98 = sum(calc_delta_exp(i) for i in range(1, 99))
        delta_99 = calc_delta_exp(99)
        total_lifetime = sum_1_98 + delta_99
        assert delta_99 >= 0.30 * sum_1_98
        assert 0.25 <= (delta_99 / total_lifetime) <= 0.35

    # --- F2: Progression Benchmarks Schema & Seeder ---
    def test_f02_progression_benchmarks_row_count(self, matrix_db_conn: sqlite3.Connection) -> None:
        """progression_benchmarks has exactly 100 rows for levels 1 to 100."""
        count = matrix_db_conn.execute("SELECT COUNT(*) FROM progression_benchmarks").fetchone()[0]
        assert count == 100

    def test_f02_progression_benchmarks_endgame_affix_tier_unlock(self, matrix_db_conn: sqlite3.Connection) -> None:
        """Level 85 allows max affix tier 1 (T1 affix unlocked)."""
        row = matrix_db_conn.execute("SELECT max_affix_tier_allowed FROM progression_benchmarks WHERE level = 85").fetchone()
        assert row is not None and row[0] == 1

    def test_f02_progression_benchmarks_delta_column(self, matrix_db_conn: sqlite3.Connection) -> None:
        """Schema contains exp_to_next_level column matching 7-segment curve."""
        row = matrix_db_conn.execute("SELECT exp_to_next_level FROM progression_benchmarks WHERE level = 1").fetchone()
        assert row is not None and row[0] == calc_delta_exp(1)

    def test_f02_progression_benchmarks_death_penalty_column(self, matrix_db_conn: sqlite3.Connection) -> None:
        """Schema contains death_penalty_ratio column populated with tiered rates."""
        row = matrix_db_conn.execute("SELECT death_penalty_ratio FROM progression_benchmarks WHERE level = 99").fetchone()
        assert row is not None and abs(row[0] - 0.25) < 1e-4

    def test_f02_progression_benchmarks_cumulative_column(self, matrix_db_conn: sqlite3.Connection) -> None:
        """Schema contains cumulative_exp matching cumulative lifetime values."""
        row = matrix_db_conn.execute("SELECT cumulative_exp FROM progression_benchmarks WHERE level = 100").fetchone()
        assert row is not None and row[0] > 10_000_000_000

    # --- F3: Level Gap Penalty Formula ---
    def test_f03_level_gap_safe_range_full_exp(self) -> None:
        """Level gap <= 5 gives 100% EXP efficiency (1.0)."""
        for gap in range(0, 6):
            assert calc_level_gap_decay(80 + gap, 80) == 1.0
            assert calc_level_gap_decay(80, 80 + gap) == 1.0

    def test_f03_level_gap_decay_starts_at_diff_6(self) -> None:
        """Level gap == 6 starts exponential decay (approx 54.88%)."""
        eff = calc_level_gap_decay(86, 80)
        assert abs(eff - math.exp(-0.60)) < 1e-4

    def test_f03_level_gap_decay_diff_10_under_5_percent(self) -> None:
        """Monster 10 levels lower gives <= 5% EXP (decay <= 0.05)."""
        eff = calc_level_gap_decay(90, 80)
        assert eff <= 0.05 and abs(eff - math.exp(-3.0)) < 1e-4

    def test_f03_level_gap_extreme_gap_floor(self) -> None:
        """Extreme gap clamps to minimum floor 0.01 (1%)."""
        eff = calc_level_gap_decay(95, 20)
        assert eff == 0.01

    def test_f03_level_gap_underleveled_anti_boosting(self) -> None:
        """Underleveled player has exponential decay to prevent power-leveling."""
        eff = calc_level_gap_decay(20, 80)
        assert eff == 0.05

    # --- F4: Tiered Hybrid Death Penalty ---
    @pytest.mark.parametrize("level,expected_ratio", [
        (1, 0.0), (30, 0.0), (60, 0.0),
        (61, 0.05), (75, 0.05), (80, 0.05),
        (81, 0.10), (85, 0.10), (89, 0.10),
        (90, 0.15), (95, 0.15), (98, 0.15),
        (99, 0.25)
    ])
    def test_f04_death_penalty_tiered_ratios(self, level: int, expected_ratio: float) -> None:
        """Tiered death penalty matches 0%, 5%, 10%, 15%, 25% across all level brackets."""
        assert calc_death_penalty_ratio(level) == expected_ratio

    def test_f04_death_penalty_safe_floor_at_zero(self) -> None:
        """Dying at 0% progress results in 0% progress; level never decreases."""
        curr_exp = 0
        delta = calc_delta_exp(95)
        loss = int(math.floor(delta * calc_death_penalty_ratio(95)))
        new_exp = max(0, curr_exp - loss)
        assert new_exp == 0

    # --- F5: LevelProgressionService Engine Contract ---
    def test_f05_award_monster_exp_contract(self) -> None:
        """LevelProgressionService.award_monster_exp credits EXP and handles gap decay."""
        from server.world.level_progression_service import LevelProgressionService
        service = LevelProgressionService()
        res = service.award_monster_exp("p1", 80, 80, 1000)
        assert res.effective_exp == 1000 and res.level_up_occurred is False

    def test_f05_level_up_awards_stats_and_passives(self) -> None:
        """Level up awards +5 stat points and +1 passive point."""
        from server.world.level_progression_service import LevelProgressionService
        service = LevelProgressionService()
        d1 = service.get_delta_exp(1)
        res = service.award_monster_exp("p2", 1, 1, d1 + 10)
        assert res.level_up_occurred is True and res.new_level == 2

    def test_f05_apply_death_penalty_contract(self) -> None:
        """apply_death_penalty subtracts tiered percentage with safe floor."""
        from server.world.level_progression_service import LevelProgressionService
        service = LevelProgressionService()
        res = service.apply_death_penalty("p3")
        assert res.exp_lost >= 0 and res.new_exp >= 0

    # --- F6: Trial 10 Ascension Level 100 Gating ---
    def test_f06_trial_10_catalog_registration(self) -> None:
        """TRIAL_10_GODHOOD_TRANSCENDENCE is registered with required_level=100 and 3 reward points."""
        trials = {t.trial_index: t for t in get_canonical_trials()}
        trial_10 = trials.get(10)
        assert trial_10 is not None
        assert trial_10.trial_id == "TRIAL_10_GODHOOD_TRANSCENDENCE"
        assert trial_10.required_level == 100
        assert trial_10.reward_points == 3

    def test_f06_trial_10_rejected_at_level_99(self) -> None:
        """Character at level 99 is strictly rejected from Trial 10."""
        from server.world.ascendancy_engine import AscendancyEngine
        engine = AscendancyEngine()
        success, msg, _ = engine.complete_trial(1, 10, player_level=99)
        assert success is False and "100" in msg

    def test_f06_trial_10_accepted_at_level_100(self) -> None:
        """Character at level 100 is accepted for Trial 10."""
        from server.world.ascendancy_engine import AscendancyEngine
        engine = AscendancyEngine()
        for t in range(1, 10):
            engine.complete_trial(2, t, player_level=100)
        success, _, pts = engine.complete_trial(2, 10, player_level=100)
        assert success is True and pts == 3

    # --- F7: Godhood Avatar Metamorphosis Keystone ---
    def test_f07_godhood_keystone_metamorphosis_flag(self) -> None:
        """AscendancyAppliedEffects contains avatar_metamorphosis_active = True."""
        effects = AscendancyAppliedEffects(avatar_metamorphosis_active=True, damage_multiplier=1.40, max_resists_bonus=0.05)
        assert effects.avatar_metamorphosis_active is True

    def test_f07_godhood_combat_mitigation_80_percent_cap(self, combat_pair: Tuple[CombatEngine, CombatActor, CombatActor]) -> None:
        """CombatEngine resistance mitigation caps at 80% when max resist bonus is applied."""
        engine, attacker, defender = combat_pair
        defender.resistances[FiveElements.HOA] = 0.85
        # Standard cap is 75%
        res = engine.calculate_damage(attacker.actor_id, defender.actor_id, 100.0, FiveElements.HOA, current_timestamp_ms=1000)
        assert res.final_damage == 25.0  # 100 * (1 - 0.75) = 25.0

    # --- F8: Monte Carlo Simulation Tool Verification ---
    def test_f08_simulation_zero_death_100_percent_success(self) -> None:
        """Zero death rate guarantees completion in season."""
        # Standard daily T16 exp budget: 75 maps * 2.5M = 187.5M exp/day
        daily_exp = 187_500_000
        delta_99 = calc_delta_exp(99)
        days_needed = math.ceil(delta_99 / daily_exp)
        assert days_needed <= 40  # Well within 90-day season

    def test_f08_simulation_high_death_rate_catastrophic_setback(self) -> None:
        """At 25% death penalty, 1 death wipes ~8 days of progress."""
        daily_exp = 187_500_000
        delta_99 = calc_delta_exp(99)
        loss_per_death = delta_99 * 0.25
        lost_days = loss_per_death / daily_exp
        assert lost_days >= 7.0


# --- Tier 2: Boundary & Corner Cases ---

class TestTier2BoundaryAndCornerCases:
    """Tier 2: Boundary & Corner Cases."""

    def test_b01_level_boundary_level_1_zero_exp(self) -> None:
        """Level 1 character starts with 0 EXP and delta is calc_delta_exp(1)."""
        assert calc_delta_exp(1) == 600

    def test_b02_level_boundary_level_100_zero_delta(self) -> None:
        """Level 100 has 0 delta exp (max cap reached)."""
        assert calc_delta_exp(100) == 0

    def test_b03_level_gap_boundary_exact_5_vs_6(self) -> None:
        """Gap 5 is exactly 1.0; Gap 6 begins exponential decay."""
        assert calc_level_gap_decay(85, 80) == 1.0
        assert calc_level_gap_decay(86, 80) < 1.0

    def test_b04_death_penalty_bracket_boundaries(self) -> None:
        """Level 60 is 0% vs Level 61 is 5%; Level 89 is 10% vs Level 90 is 15%."""
        assert calc_death_penalty_ratio(60) == 0.0 and calc_death_penalty_ratio(61) == 0.05
        assert calc_death_penalty_ratio(89) == 0.10 and calc_death_penalty_ratio(90) == 0.15
        assert calc_death_penalty_ratio(98) == 0.15 and calc_death_penalty_ratio(99) == 0.25

    def test_b05_death_penalty_clamped_from_10_percent_to_zero(self) -> None:
        """Character at 10% progress losing 25% clamps at 0% without de-leveling."""
        delta = calc_delta_exp(99)
        curr = int(delta * 0.10)
        penalty = int(delta * 0.25)
        new_exp = max(0, curr - penalty)
        assert new_exp == 0


# --- Tier 3: Cross-Feature Combinations ---

class TestTier3CrossFeatureCombinations:
    """Tier 3: Cross-Feature Combinations (Pairwise interactions)."""

    def test_c01_level_up_shifts_level_gap_penalty(self) -> None:
        """Advancing level shifts level gap from full 100% to decayed yield."""
        # At level 85 vs monster 80, gap is 5 (1.0)
        assert calc_level_gap_decay(85, 80) == 1.0
        # Upon level up to 86, gap is 6 (0.5488)
        assert calc_level_gap_decay(86, 80) < 0.60

    def test_c02_combat_lethal_damage_triggers_tiered_penalty(self, combat_pair: Tuple[CombatEngine, CombatActor, CombatActor]) -> None:
        """Combat fatal blow reduces player HP to 0 and invokes death penalty."""
        engine, attacker, defender = combat_pair
        attacker.base_attack = 5000.0  # Lethal strike
        res = engine.calculate_damage(attacker.actor_id, defender.actor_id, 5000.0, FiveElements.HOA, current_timestamp_ms=1000)
        assert defender.current_hp == 0.0 and res.final_damage >= 1000.0
        # Post-death penalty at level 92 is 15%
        ratio = calc_death_penalty_ratio(92)
        assert ratio == 0.15

    def test_c03_metamorphosis_power_spike_damage_amplification(self, combat_pair: Tuple[CombatEngine, CombatActor, CombatActor]) -> None:
        """Metamorphosis +40% More Damage scales raw damage from 100.0 to 140.0."""
        engine, attacker, defender = combat_pair
        base_dmg = 100.0
        boosted_dmg = base_dmg * 1.40
        res = engine.calculate_damage(attacker.actor_id, defender.actor_id, boosted_dmg, FiveElements.HOA, current_timestamp_ms=1000)
        assert res.raw_damage == 140.0


# --- Tier 4: Real-World Application Scenarios ---

class TestTier4RealWorldScenarios:
    """Tier 4: Real-World Application Scenarios."""

    def test_r01_full_progression_journey_simulation(self) -> None:
        """Simulates full progression journey from Level 1 to Level 100."""
        current_level = 1
        current_exp = 0
        total_exp_earned = 0
        while current_level < 100:
            delta = calc_delta_exp(current_level)
            current_exp += delta
            total_exp_earned += delta
            current_level += 1
            current_exp = 0
        assert current_level == 100
        assert total_exp_earned == sum(calc_delta_exp(i) for i in range(1, 100))

    def test_r02_hardcore_death_streak_at_level_99(self) -> None:
        """Simulates 5 consecutive deaths at Level 99 losing 25% each time with safe floor."""
        delta = calc_delta_exp(99)
        current_exp = int(delta * 0.85)  # 85% progress
        loss_per_death = int(delta * 0.25)
        progression_trace = []
        for _ in range(5):
            current_exp = max(0, current_exp - loss_per_death)
            progression_trace.append(current_exp)
        # Expected: 60% -> 35% -> 10% -> 0% -> 0%
        assert progression_trace[0] == int(delta * 0.85) - loss_per_death
        assert progression_trace[1] == int(delta * 0.85) - 2 * loss_per_death
        assert progression_trace[2] == int(delta * 0.85) - 3 * loss_per_death
        assert progression_trace[3] == 0
        assert progression_trace[4] == 0
        assert abs(progression_trace[1] - int(delta * 0.35)) <= 5

    def test_r03_anti_bot_farm_deterrence_scenario(self) -> None:
        """Level 95 bot farming level 20 mobs receives only 1% EXP."""
        eff = calc_level_gap_decay(95, 20)
        assert eff == 0.01  # Clamped to minimum floor
