"""Unit tests for bannerlord.models — data contracts and reward functions."""

import pytest

from bannerlord.models import (
    SUBGOAL_TOKENS,
    DiplomacyReward,
    EconomyReward,
    KingSubgoal,
    ResultMessage,
    StateUpdateMessage,
    SubgoalMessage,
    TaskMessage,
    VictoryCondition,
    WarReward,
)


# ── KingSubgoal ───────────────────────────────────────────────────────────────


class TestKingSubgoal:
    def test_valid_token(self):
        s = KingSubgoal(token="EXPAND_TERRITORY", target="Epicrotea")
        assert s.token == "EXPAND_TERRITORY"
        assert s.target == "Epicrotea"
        assert s.priority == 1.0

    def test_all_tokens_valid(self):
        for token in SUBGOAL_TOKENS:
            KingSubgoal(token=token)

    def test_invalid_token_raises(self):
        with pytest.raises(ValueError, match="Unknown subgoal token"):
            KingSubgoal(token="NUKE_CALRADIA")

    def test_priority_clamp(self):
        with pytest.raises(Exception):
            KingSubgoal(token="TRADE", priority=3.0)

    def test_optional_fields_default_none(self):
        s = KingSubgoal(token="HEAL")
        assert s.target is None
        assert s.quantity is None
        assert s.deadline_days is None
        assert s.context is None


# ── Messages ──────────────────────────────────────────────────────────────────


class TestSubgoalMessage:
    def test_defaults(self):
        msg = SubgoalMessage(
            to_agent="war_vassal",
            subgoal=KingSubgoal(token="RAID_ECONOMY"),
        )
        assert msg.msg_type == "subgoal"
        assert msg.from_agent == "king"
        assert msg.to_agent == "war_vassal"
        assert msg.issued_at is not None

    def test_subgoal_roundtrip(self):
        subgoal = KingSubgoal(token="RECRUIT", quantity=30, priority=1.5)
        msg = SubgoalMessage(to_agent="war_vassal", subgoal=subgoal)
        assert msg.subgoal.quantity == 30
        assert msg.subgoal.priority == 1.5


class TestTaskMessage:
    def test_construction(self):
        t = TaskMessage(
            from_agent="war_vassal",
            to_agent="logistics_companion",
            primitive="recruit_troop",
            args={"troop_type": "cavalry", "quantity": 5},
            priority=1.2,
        )
        assert t.msg_type == "task"
        assert t.primitive == "recruit_troop"
        assert t.args["quantity"] == 5


class TestResultMessage:
    def test_success(self):
        r = ResultMessage(
            from_agent="logistics_companion",
            to_agent="war_vassal",
            success=True,
            outcome={"recruited": 10},
            reward_delta=0.15,
        )
        assert r.success is True
        assert r.reward_delta == 0.15

    def test_failure(self):
        r = ResultMessage(
            from_agent="scout_companion",
            to_agent="diplomacy_vassal",
            success=False,
            outcome={"error": "GABS unavailable"},
        )
        assert r.success is False
        assert r.reward_delta == 0.0


class TestStateUpdateMessage:
    def test_construction(self):
        msg = StateUpdateMessage(
            game_state={"campaign_day": 42, "player_title": "Lord"},
            tick=42,
        )
        assert msg.msg_type == "state"
        assert msg.tick == 42
        assert msg.game_state["campaign_day"] == 42


# ── Reward functions ──────────────────────────────────────────────────────────


class TestWarReward:
    def test_positive_expansion(self):
        r = WarReward(territory_delta=2.0, army_strength_ratio=1.2, subgoal_bonus=0.1)
        assert r.total > 0

    def test_casualty_cost_penalizes(self):
        no_cost = WarReward(territory_delta=1.0, army_strength_ratio=1.0)
        with_cost = WarReward(territory_delta=1.0, army_strength_ratio=1.0, casualty_cost=5.0)
        assert with_cost.total < no_cost.total

    def test_zero_state(self):
        r = WarReward()
        # army_strength_ratio default 1.0, rest 0 → 0.25 * 1.0 = 0.25
        assert abs(r.total - 0.25) < 1e-9


class TestEconomyReward:
    def test_income_positive(self):
        r = EconomyReward(daily_denars_income=100.0, food_stock_buffer=7.0, loyalty_average=80.0)
        assert r.total > 0

    def test_construction_queue_penalizes(self):
        no_queue = EconomyReward(daily_denars_income=50.0)
        long_queue = EconomyReward(daily_denars_income=50.0, construction_queue_length=10)
        assert long_queue.total < no_queue.total

    def test_loyalty_contributes(self):
        low_loyalty = EconomyReward(loyalty_average=10.0)
        high_loyalty = EconomyReward(loyalty_average=90.0)
        assert high_loyalty.total > low_loyalty.total


class TestDiplomacyReward:
    def test_allies_positive(self):
        r = DiplomacyReward(allies_count=3)
        assert r.total > 0

    def test_active_wars_penalizes(self):
        peace = DiplomacyReward(allies_count=2)
        war = DiplomacyReward(allies_count=2, active_wars_front=4)
        assert war.total < peace.total


# ── Victory condition ─────────────────────────────────────────────────────────


class TestVictoryCondition:
    def test_not_achieved_without_title(self):
        v = VictoryCondition(holds_king_title=False, territory_control_pct=70.0)
        assert not v.achieved

    def test_not_achieved_without_majority(self):
        v = VictoryCondition(holds_king_title=True, territory_control_pct=40.0)
        assert not v.achieved

    def test_achieved_when_king_with_majority(self):
        v = VictoryCondition(holds_king_title=True, territory_control_pct=55.0)
        assert v.achieved

    def test_exact_threshold(self):
        v = VictoryCondition(holds_king_title=True, territory_control_pct=51.0)
        assert v.achieved

    def test_custom_threshold(self):
        v = VictoryCondition(
            holds_king_title=True,
            territory_control_pct=70.0,
            majority_threshold=75.0,
        )
        assert not v.achieved
