"""
Unit Tests for FreeExile Game Design Matrix Service & Integrity Engine.
Verifies cross-cutting queries, DAG cycle detection, foreign key enforcement,
and automated anti-drift validation.
"""

from __future__ import annotations
import pytest
from server.world.game_design_matrix_service import GameDesignMatrixService


@pytest.fixture
def matrix_service():
    """Provides an in-memory matrix service populated with canonical data."""
    service = GameDesignMatrixService(db_path=":memory:")
    service.seed_canonical_data(force=True)
    yield service
    service.close()


def test_canonical_seeding_counts(matrix_service: GameDesignMatrixService):
    """Verifies that all core tables are populated with expected canonical counts."""
    report = matrix_service.validate_game_design_integrity()
    assert report.is_valid is True
    assert report.total_acts == 5
    assert report.total_zones >= 4
    assert report.total_npcs >= 5
    assert report.total_quests >= 10
    assert report.total_monsters >= 50
    assert report.total_skills >= 10
    assert report.total_weapons >= 20
    assert report.total_affixes >= 100
    assert report.total_relations >= 20
    assert len(report.violations) == 0


def test_act_narrative_context_query(matrix_service: GameDesignMatrixService):
    """Tests fetching complete narrative context of an Act."""
    context = matrix_service.get_act_narrative_context("ACT_I_HUNGER")
    assert context is not None
    assert context["act"]["act_id"] == "ACT_I_HUNGER"
    assert "Đáy Vực Đói Rét" in context["act"]["title"]
    assert context["act"]["primary_zone_id"] == "zone_tang_kiem_nhai"
    assert context["act"]["climax_boss_id"] == "boss_strand_executioner"
    assert len(context["quests"]) > 0


def test_zone_ecosystem_query(matrix_service: GameDesignMatrixService):
    """Tests retrieving full zone ecosystem including NPCs and quests."""
    eco = matrix_service.get_zone_ecosystem("zone_boundless_sanctuary")
    assert eco is not None
    assert eco["zone"]["zone_id"] == "zone_boundless_sanctuary"
    assert len(eco["npcs"]) >= 2
    # Verify NPC roles
    roles = {npc["role"] for npc in eco["npcs"]}
    assert "BONE_BLACKSMITH" in roles or "BLOOD_SHAMAN" in roles


def test_quest_context_and_dependency_chain(matrix_service: GameDesignMatrixService):
    """Tests quest ecosystem view and prerequisite chain backtracking."""
    q_ctx = matrix_service.get_quest_context("quest_norm_04_diet_u_hon_kiem_no")
    assert q_ctx is not None
    assert q_ctx["act_id"] == "ACT_II_MIASMA"
    assert q_ctx["prerequisite_quest_id"] == "quest_norm_03_luyen_khi_hoa_thach"

    # Dependency chain
    chain = matrix_service.get_quest_dependency_chain("quest_norm_04_diet_u_hon_kiem_no")
    assert chain == [
        "quest_norm_03_luyen_khi_hoa_thach",
        "quest_norm_02_khai_thong_dan_dien",
        "quest_norm_01_thuc_tinh_tang_kiem"
    ]


def test_level_progression_benchmark(matrix_service: GameDesignMatrixService):
    """Tests mathematical progression milestone lookups."""
    b_lvl1 = matrix_service.get_level_progression_benchmark(1)
    assert b_lvl1 is not None
    assert b_lvl1.level == 1
    assert b_lvl1.player_base_hp == 100.0
    assert b_lvl1.max_affix_tier_allowed == 15

    b_lvl85 = matrix_service.get_level_progression_benchmark(85)
    assert b_lvl85 is not None
    assert b_lvl85.level == 85
    assert b_lvl85.player_base_hp > b_lvl1.player_base_hp
    assert b_lvl85.max_affix_tier_allowed == 1  # T1 unlocked at 85


def test_dag_cycle_detection_failure(matrix_service: GameDesignMatrixService):
    """Verifies that an intentional circular dependency in quests causes validation to FAIL."""
    with matrix_service._get_connection() as conn:
        cur = conn.cursor()
        # Create a circular loop: norm_01 -> norm_03
        cur.execute("""
            UPDATE quests 
            SET prerequisite_quest_id = 'quest_norm_03_luyen_khi_hoa_thach' 
            WHERE quest_id = 'quest_norm_01_thuc_tinh_tang_kiem'
        """)

    report = matrix_service.validate_game_design_integrity()
    assert report.is_valid is False
    assert any("Circular Dependency in Quest DAG" in v for v in report.violations)


def test_broken_prerequisite_detection(matrix_service: GameDesignMatrixService):
    """Verifies that referencing a non-existent prerequisite quest triggers a violation."""
    with matrix_service._get_connection() as conn:
        cur = conn.cursor()
        # Temporarily disable foreign keys to force a broken ID for testing the linter check
        cur.execute("PRAGMA foreign_keys = OFF")
        cur.execute("""
            UPDATE quests 
            SET prerequisite_quest_id = 'quest_non_existent_phantom_ghost' 
            WHERE quest_id = 'quest_norm_01_thuc_tinh_tang_kiem'
        """)
        cur.execute("PRAGMA foreign_keys = ON")

    report = matrix_service.validate_game_design_integrity()
    assert report.is_valid is False
    assert any("Broken Quest Prerequisite" in v for v in report.violations)


def test_cross_relations_query(matrix_service: GameDesignMatrixService):
    """Tests querying cross-relationships by source entity."""
    rels = matrix_service.query_cross_relations(source_type="ACT", source_id="ACT_I_HUNGER")
    assert len(rels) >= 2
    types = {r["relation_type"] for r in rels}
    assert "ACT_CONTAINS_ZONE" in types
    assert "BOSS_CLIMAX_FOR_ACT" in types


def test_party_scaling_matrix_queries_and_monotonicity(matrix_service: GameDesignMatrixService):
    """Tests party scaling lookup from size 1 to 6 and monotonic growth."""
    solo = matrix_service.get_party_scaling(1)
    assert solo is not None
    assert solo.hp_multiplier == 1.0
    assert solo.exp_bonus_pct == 0.0
    assert solo.item_quantity_bonus_pct == 0.0
    assert solo.nearby_radius == 15.0

    max_party = matrix_service.get_party_scaling(6)
    assert max_party is not None
    assert max_party.hp_multiplier == 3.5
    assert max_party.exp_bonus_pct == 150.0
    assert max_party.item_quantity_bonus_pct == 250.0
    assert max_party.currency_quantity_bonus_pct == 250.0
    assert max_party.rarity_bonus_pct == 150.0
    assert max_party.nearby_radius == 15.0

    # Non-existent size
    assert matrix_service.get_party_scaling(99) is None

    # All scalings monotonic
    scalings = matrix_service.get_all_party_scalings()
    assert len(scalings) == 6
    for i in range(len(scalings) - 1):
        assert scalings[i + 1].hp_multiplier > scalings[i].hp_multiplier
        assert scalings[i + 1].exp_bonus_pct > scalings[i].exp_bonus_pct
        assert scalings[i + 1].item_quantity_bonus_pct > scalings[i].item_quantity_bonus_pct


def test_loot_allocation_modes_queries(matrix_service: GameDesignMatrixService):
    """Tests querying 3 canonical loot allocation modes and timeout configs."""
    perm = matrix_service.get_loot_allocation_mode("PERMANENT")
    assert perm is not None
    assert perm.is_permanent is True
    assert perm.timeout_seconds == 0.0

    short_mode = matrix_service.get_loot_allocation_mode("SHORT")
    assert short_mode is not None
    assert short_mode.is_permanent is False
    assert short_mode.timeout_seconds == 5.0

    ffa = matrix_service.get_loot_allocation_mode("FREE_FOR_ALL")
    assert ffa is not None
    assert ffa.is_permanent is False
    assert ffa.timeout_seconds == 0.0

    assert matrix_service.get_loot_allocation_mode("NON_EXISTENT") is None
    all_modes = matrix_service.get_all_loot_allocation_modes()
    assert len(all_modes) == 3


def test_teleport_configs_queries(matrix_service: GameDesignMatrixService):
    """Tests teleport config queries and safety channel flags."""
    party_tele = matrix_service.get_teleport_config("PARTY_MEMBER")
    assert party_tele is not None
    assert party_tele.cast_time_seconds == 3.5
    assert party_tele.cooldown_seconds == 10.0
    assert party_tele.cancel_on_movement is True
    assert party_tele.cancel_on_damage is True

    portal_tele = matrix_service.get_teleport_config("TOWN_PORTAL")
    assert portal_tele is not None
    assert portal_tele.cast_time_seconds == 3.5

    all_configs = matrix_service.get_all_teleport_configs()
    assert len(all_configs) == 4


def test_portal_quarantine_rules_queries(matrix_service: GameDesignMatrixService):
    """Tests quarantine rules for wilderness, dungeon, secret chamber, and safe haven."""
    wild_rule = matrix_service.get_portal_quarantine_rule("WILDERNESS")
    assert wild_rule is not None
    assert wild_rule.boss_lockout is True
    assert wild_rule.secret_chamber_lockout is True
    assert wild_rule.max_portals == 6

    secret_rule = matrix_service.get_portal_quarantine_rule("SECRET_CHAMBER")
    assert secret_rule is not None
    assert secret_rule.boss_lockout is True
    assert secret_rule.secret_chamber_lockout is True
    assert secret_rule.max_portals == 0

    haven_rule = matrix_service.get_portal_quarantine_rule("SAFE_HAVEN")
    assert haven_rule is not None
    assert haven_rule.boss_lockout is False
    assert haven_rule.secret_chamber_lockout is False

    all_rules = matrix_service.get_all_portal_quarantine_rules()
    assert len(all_rules) == 4


def test_party_scaling_non_monotonic_detection(matrix_service: GameDesignMatrixService):
    """Verifies that non-monotonic party scaling triggers an integrity violation."""
    with matrix_service._get_connection() as conn:
        conn.cursor().execute("UPDATE party_scaling_matrix SET hp_multiplier = 1.2 WHERE party_size = 3")
    report = matrix_service.validate_game_design_integrity()
    assert report.is_valid is False
    assert any("Party Scaling HP non-monotonic" in v for v in report.violations)


def test_loot_allocation_invalid_timeout_detection(matrix_service: GameDesignMatrixService):
    """Verifies that invalid SHORT timeout triggers an integrity violation."""
    with matrix_service._get_connection() as conn:
        conn.cursor().execute("UPDATE loot_allocation_modes SET timeout_seconds = 2.0 WHERE mode_id = 'SHORT'")
    report = matrix_service.validate_game_design_integrity()
    assert report.is_valid is False
    assert any("Loot Allocation SHORT timeout expected 5.0s" in v for v in report.violations)


def test_teleport_config_invalid_cast_time_detection(matrix_service: GameDesignMatrixService):
    """Verifies that altered teleport cast time triggers an integrity violation."""
    with matrix_service._get_connection() as conn:
        conn.cursor().execute("UPDATE teleport_configs SET cast_time_seconds = 1.0 WHERE action_type = 'PARTY_MEMBER'")
    report = matrix_service.validate_game_design_integrity()
    assert report.is_valid is False
    assert any("Teleport Config 'PARTY_MEMBER' invalid" in v for v in report.violations)

