"""
Unit tests for Agent Orb Delegation Service in FreeExile.
Verifies Agent_Orb inventory consumption, session activation, offline persistence,
harvest tracking (EXP, Linh Thạch, kills), and player control reclamation.
"""

import unittest
import sys
import os

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../server")))
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))

from world.server_engine_loop import ServerEngineLoop
from world.martial_matrix import FiveElements
from world.agent_orb_service import (
    AgentOrbService,
    AgentOrbTier,
    DelegationSession,
    DelegationHarvestReport
)
from agent.agent_decision_core import TacticalStance
from agent.llm_provider_client import LLMProviderType, LLMProviderConfig


class TestAgentOrbService(unittest.TestCase):
    def setUp(self):
        self.engine = ServerEngineLoop()
        self.service = AgentOrbService(engine=self.engine, db_path=":memory:")
        # Register player 1
        self.engine.register_player(
            entity_id=1,
            initial_x=0.0,
            initial_y=0.0,
            initial_z=0.0,
            element=FiveElements.HOA,
            move_speed=6.0,
            max_hp=1000.0
        )
        # Grant 2x Tier 1 Orbs and 1x Tier 2 Orb
        self.service.grant_agent_orb(player_id=1, orb_id="item_agent_orb_tier1", count=2)
        self.service.grant_agent_orb(player_id=1, orb_id="item_agent_orb_tier2", count=1)

    def test_grant_and_inventory_balance(self):
        """Verifies player orb balance tracking."""
        balance = self.service.get_player_orb_balance(player_id=1)
        self.assertEqual(balance.get("item_agent_orb_tier1"), 2)
        self.assertEqual(balance.get("item_agent_orb_tier2"), 1)
        self.assertEqual(balance.get("item_agent_orb_tier3", 0), 0)

    def test_activate_delegation_consumes_orb(self):
        """Activating delegation consumes 1 orb and creates an active session with correct duration."""
        cfg = LLMProviderConfig(provider=LLMProviderType.DEEPSEEK, api_key="sk-ds-dummy")
        session = self.service.activate_delegation(
            player_id=1,
            orb_id="item_agent_orb_tier1",
            stance=TacticalStance.AGGRESSIVE,
            llm_config=cfg
        )
        self.assertIsNotNone(session)
        self.assertTrue(session.is_active)
        self.assertEqual(session.total_duration_sec, 7200)  # Tier 1 = 2 hours
        self.assertEqual(session.remaining_duration_sec, 7200)
        self.assertEqual(session.stance, TacticalStance.AGGRESSIVE)
        self.assertEqual(session.llm_config.provider, LLMProviderType.DEEPSEEK)

        # Inventory balance must decrement to 1
        self.assertEqual(self.service.get_player_orb_balance(1)["item_agent_orb_tier1"], 1)

    def test_offline_delegation_continuation(self):
        """When player goes offline, session is flagged as offline and continues autonomous gameplay."""
        cfg = LLMProviderConfig(provider=LLMProviderType.LOCAL_TACTICAL)
        self.service.activate_delegation(1, "item_agent_orb_tier1", TacticalStance.BALANCED, cfg)

        self.service.set_player_offline_state(player_id=1, is_offline=True)
        session = self.service.get_active_session(player_id=1)
        self.assertIsNotNone(session)
        self.assertTrue(session.is_offline)
        self.assertTrue(session.is_active)

    def test_extend_delegation_stacks_duration(self):
        """Using a second orb extends the remaining duration without resetting harvest stats."""
        cfg = LLMProviderConfig(provider=LLMProviderType.LOCAL_TACTICAL)
        session = self.service.activate_delegation(1, "item_agent_orb_tier1", TacticalStance.BALANCED, cfg)
        initial_remaining = session.remaining_duration_sec

        # Extend with Tier 2 orb (+28,800 sec / 8h)
        extended = self.service.extend_delegation(player_id=1, orb_id="item_agent_orb_tier2")
        self.assertTrue(extended)
        self.assertEqual(session.remaining_duration_sec, initial_remaining + 28800)
        self.assertEqual(self.service.get_player_orb_balance(1)["item_agent_orb_tier2"], 0)

    def test_tick_harvest_accumulation_and_expiration(self):
        """Verifies harvest increments and automatic termination upon timer expiry."""
        cfg = LLMProviderConfig(provider=LLMProviderType.LOCAL_TACTICAL)
        session = self.service.activate_delegation(1, "item_agent_orb_tier1", TacticalStance.BALANCED, cfg)
        # Manually set remaining duration to 2.0 seconds for quick testing
        session.remaining_duration_sec = 2.0

        # Record some loot events
        self.service.record_loot_harvest(player_id=1, item_id="curr_chaos_stone", count=3)
        self.service.record_monster_slain(player_id=1, exp=1500)

        # Step 1 second
        self.service.step_tick(dt=1.0)
        self.assertTrue(session.is_active)
        self.assertAlmostEqual(session.remaining_duration_sec, 1.0)

        # Step 2 seconds -> should expire
        self.service.step_tick(dt=2.0)
        self.assertFalse(session.is_active)
        self.assertTrue(session.is_expired)

    def test_reclaim_control_returns_harvest_report(self):
        """Player logs in, clicks 'Reclaim Control', and receives comprehensive harvest summary."""
        cfg = LLMProviderConfig(provider=LLMProviderType.LOCAL_TACTICAL)
        self.service.activate_delegation(1, "item_agent_orb_tier1", TacticalStance.TREASURE_HUNTER, cfg)
        self.service.record_loot_harvest(1, "curr_chaos_stone", 5)
        self.service.record_loot_harvest(1, "curr_destiny_rune", 1)
        self.service.record_monster_slain(1, exp=8500)

        report = self.service.reclaim_control(player_id=1)
        self.assertIsInstance(report, DelegationHarvestReport)
        self.assertEqual(report.player_id, 1)
        self.assertEqual(report.monsters_slain, 1)
        self.assertEqual(report.exp_gained, 8500)
        self.assertEqual(report.spirit_stones_looted.get("curr_chaos_stone"), 5)
        self.assertEqual(report.spirit_stones_looted.get("curr_destiny_rune"), 1)

        # Session should no longer be active
        self.assertIsNone(self.service.get_active_session(1))


if __name__ == "__main__":
    unittest.main()
