"""
Demonstrates + sanity-checks the optimizer (roadmap step 4, first cut):
goal presets (softcap defense, recharge target, simplified max-DPS) each
run in both search-space modes (slotting-only with fixed powers, and
powers+slotting choosing among optional pool powersets).

This is deliberately a SMALL search space (a handful of powers) so it runs
in seconds and its result is easy to sanity-check by hand against the
already-validated calc-engine (build_stats.py / set_bonus_calc.py) rather
than trusting the search blindly. It is NOT meant to demonstrate realistic
whole-build optimization (a real 45%-defense softcap build needs far more
than 3 toggles) - see README for that scope note.
"""
from db import get_default_db
from attrib_mod import AttribMods
from search_space import BuildSkeleton, FixedPowerSpec, OptionalPowersetSpec, SearchSpace
from goals import SoftcapDefenseGoal, RechargeTargetGoal, MaxDPSGoal
from simulated_annealing import optimize
from build_model import power_slotted_enhancement_uids

db = get_default_db()
attrib_mods = AttribMods(db.data_dir + "/AttribMod.json")

print("=" * 70)
print("TEST 1: slotting-only - softcap Ranged defense with fewest slots")
print("=" * 70)
skeleton = BuildSkeleton(
    class_name="Class_Blaster",
    fixed_powers=[
        FixedPowerSpec("Pool.Leadership.Defense", 3),
        FixedPowerSpec("Pool.Fighting.Weave", 3),
        FixedPowerSpec("Pool.Leaping.Combat_Jumping", 3),
    ],
)
# NOTE ON THE TARGET: with only 3 toggles (9 slots) in this deliberately small demo
# skeleton, the real ceiling on achievable Ranged defense is roughly ~14% (checked by
# running the search with an unreachable 99% target and seeing where it plateaus) - a
# real "softcap to 45%" build needs defense contributions from far more powers than
# this. 10% is comfortably below that ceiling so this test demonstrates the search
# actually reaching a met target, not just climbing forever.
space = SearchSpace(db, skeleton)
goal = SoftcapDefenseGoal(defense_type="Ranged", target=0.10, slot_penalty=1.0)
result = optimize(db, attrib_mods, space, goal, iterations=3000, seed=42, progress_every=1000)
print(f"\nBest found: {goal.describe(result['stats'])}")
for pe in result["build"].power_entries:
    uids = [s.enhancement.uid if s.enhancement else None for s in pe.slot_entries]
    print(f"  {pe.power_name}: {uids}")
assert result["stats"]["defense"].get("Ranged", 0.0) >= goal.target, \
    f"expected the 10% Ranged defense target to be reachable with 3 toggles, got {result['stats']['defense'].get('Ranged', 0.0)*100:.2f}%"

print("\n" + "=" * 70)
print("TEST 2: slotting-only - reach +20% global recharge with fewest slots")
print("=" * 70)
goal2 = RechargeTargetGoal(target=0.20, slot_penalty=1.0)
result2 = optimize(db, attrib_mods, space, goal2, iterations=1500, seed=7, progress_every=500)
print(f"\nBest found: {goal2.describe(result2['stats'])}")
for pe in result2["build"].power_entries:
    uids = [s.enhancement.uid if s.enhancement else None for s in pe.slot_entries]
    print(f"  {pe.power_name}: {uids}")
lotg_pieces_used = sum(
    1 for pe in result2["build"].power_entries for s in pe.slot_entries
    if s.enhancement and "Luck_of_the_Gambler" in s.enhancement.uid
)
print(f"  Luck of the Gambler pieces used: {lotg_pieces_used}")
assert result2["stats"]["global_recharge_bonus"] >= goal2.target, \
    f"expected the +20% recharge target to be reachable with 3 toggles, got {result2['stats']['global_recharge_bonus']*100:.2f}%"
# The known efficient strategy here is exactly one LotG unique per toggle (each is a
# "special" bonus independent of piece count) - confirm the search actually found it
# rather than brute-force full-set-slotting every toggle.
assert result2["stats"]["total_slots_used"] <= 5, \
    f"expected an efficient (few-slot) solution using LotG uniques, used {result2['stats']['total_slots_used']} slots"

print("\n" + "=" * 70)
print("TEST 3: powers+slotting - choose optional Leadership/Fighting/Leaping")
print("        powers (up to 2 total) AND their slotting, for recharge")
print("=" * 70)
skeleton3 = BuildSkeleton(
    class_name="Class_Blaster",
    fixed_powers=[],
    optional_powersets=[
        OptionalPowersetSpec("Pool.Leadership", max_picks=1, slots_per_pick=2),
        OptionalPowersetSpec("Pool.Fighting", max_picks=1, slots_per_pick=2),
        OptionalPowersetSpec("Pool.Leaping", max_picks=1, slots_per_pick=2),
    ],
)
space3 = SearchSpace(db, skeleton3)
goal3 = RechargeTargetGoal(target=0.15, slot_penalty=1.0)
result3 = optimize(db, attrib_mods, space3, goal3, iterations=2000, seed=99, progress_every=500)
print(f"\nBest found: {goal3.describe(result3['stats'])}")
print(f"  Powers selected: {[pe.power_name for pe in result3['build'].power_entries]}")
for pe in result3["build"].power_entries:
    uids = [s.enhancement.uid if s.enhancement else None for s in pe.slot_entries]
    print(f"  {pe.power_name}: {uids}")
assert len(result3["build"].power_entries) > 0, "search should have picked at least one optional power"

print("\n" + "=" * 70)
print("TEST 4: slotting-only - simplified max-DPS goal on one attack power")
print("=" * 70)
skeleton4 = BuildSkeleton(
    class_name="Class_Blaster",
    fixed_powers=[FixedPowerSpec("Blaster_Ranged.Fire_Blast.Fire_Blast", 6)],
)
space4 = SearchSpace(db, skeleton4)
goal4 = MaxDPSGoal(db, attack_chain_power_names=["Blaster_Ranged.Fire_Blast.Fire_Blast"], slot_budget=6)
result4 = optimize(db, attrib_mods, space4, goal4, iterations=1500, seed=3, progress_every=500)
print(f"\nBest found: {goal4.describe(result4['stats'])}")
for pe in result4["build"].power_entries:
    uids = [s.enhancement.uid if s.enhancement else None for s in pe.slot_entries]
    print(f"  {pe.power_name}: {uids}")
# A reasonable slotting for pure single-power DPS should be dominated by damage/accuracy/recharge
# IOs, not e.g. all Endurance Reduction - sanity check by comparing against an all-empty baseline.
empty_stats_score = goal4.score({"per_power": {"Blaster_Ranged.Fire_Blast.Fire_Blast": {}},
                                  "global_recharge_bonus": 0.0, "total_slots_used": 0})
assert result4["score"] > empty_stats_score, "optimized slotting should beat an unslotted power"

print("\nAll optimizer smoke tests passed.")
