"""
Unit Tests for the 4 Autonomous Agent Power Tools in FreeExile:
1. ARPG Crafting & Economy Monte Carlo Simulator
2. Meridian Graph Topology & Passive Tree Inspector
3. Asset Pipeline & Apple Metal Shader QA Linter
4. Headless Protobuf & 2PC Security Stress Fuzzer
"""

from __future__ import annotations
import pytest
import asyncio
from tools.math.arpg_crafting_simulator import ArpgCraftingSimulator, CraftingTarget, CraftingMethod
from tools.world.meridian_graph_inspector import MeridianGraphInspector, MeridianNode, NodeType
from tools.lint.verify_asset_pipeline import is_power_of_two, AssetQaReport, audit_metal_shader_code
from tools.security.headless_packet_fuzzer import HeadlessPacketFuzzer


def test_arpg_crafting_monte_carlo():
    """Verifies that the Monte Carlo Crafting engine computes valid probabilistic stats."""
    sim = ArpgCraftingSimulator(ilvl_filter=85)
    # Target: 1 mod of tier <= 3 (very easy to hit quickly)
    target = CraftingTarget(target_tier=3, min_matching_mods=1, item_level=85)
    res = sim.run_monte_carlo(CraftingMethod.CHAOS_SPAM, target, iterations=20)

    assert res.total_trials == 20
    assert res.successful_crafts == 20
    assert res.avg_attempts_per_success > 0
    assert "HUYET_HON_THACH" in res.total_currency_spent
    assert "GOLD_FEE" in res.total_currency_spent


def test_corruption_gamble_rates():
    """Verifies that Corruption Gamble honors the ~25% brick destruction rate."""
    sim = ArpgCraftingSimulator()
    target = CraftingTarget()
    res = sim.run_monte_carlo(CraftingMethod.CORRUPTION_GAMBLE, target, iterations=200)

    assert res.total_trials == 200
    # Expected bricks ~ 25% (around 50, bounds [20, 80])
    assert 20 <= res.bricked_items <= 80
    assert res.successful_crafts > 0


def test_meridian_graph_dijkstra_and_connectivity():
    """Verifies shortest path and island node detection on the Meridian Graph."""
    inspector = MeridianGraphInspector()
    inspector.load_canonical_meridian_tree()

    # Shortest path between origin and keystone 301
    cost, path = inspector.dijkstra_shortest_path("node_origin_dantian", "node_passive_301")
    assert cost == 1
    assert path == ["node_origin_dantian", "node_passive_301"]

    # Shortest path between 301 and 304
    cost_304, path_304 = inspector.dijkstra_shortest_path("node_passive_301", "node_passive_304")
    assert cost_304 > 0
    assert len(path_304) >= 3

    # All canonical nodes must be connected (0 isolated)
    isolated = inspector.find_isolated_nodes("node_origin_dantian")
    assert len(isolated) == 0


def test_asset_pipeline_power_of_two():
    """Verifies texture power-of-two validation logic."""
    assert is_power_of_two(256) is True
    assert is_power_of_two(512) is True
    assert is_power_of_two(1024) is True
    assert is_power_of_two(300) is False
    assert is_power_of_two(1280) is False


def test_metal_shader_alignment_audit(tmp_path):
    """Verifies detection of unpadded float3 in Metal shader struct."""
    bad_shader = tmp_path / "BadShader.metal"
    bad_shader.write_text("""
        struct BadUniforms {
            float3 lightDir;
            float intensity;
        };
    """, encoding="utf-8")

    report = AssetQaReport()
    audit_metal_shader_code(str(bad_shader), report)
    assert len(report.metal_alignment_warnings) == 1
    assert "BadUniforms" in report.metal_alignment_warnings[0]


@pytest.mark.asyncio
async def test_headless_2pc_double_spend_repelled():
    """Verifies that concurrent double-spend race condition attacks are 100% repelled."""
    fuzzer = HeadlessPacketFuzzer(concurrency=15)
    total_att, total_succ, total_prev = await fuzzer.fuzz_2pc_double_spend_race(runs=3)

    assert total_att == 45
    assert total_succ == 3  # Exactly 1 success per item across 3 runs
    assert total_prev == 42 # 42 illegal duplicate claims repelled


def test_headless_speedhack_intercepted():
    """Verifies that speedhacks are clamped and logged."""
    fuzzer = HeadlessPacketFuzzer()
    attempts, blocked = fuzzer.fuzz_movement_speedhacks(attempts=50)
    assert attempts == 50
    assert blocked == 50
