"""
Tests for ActionPrompt model

Tests the action prompt availability logic, tier filtering,
location filtering, and serialization.
"""

import pytest

from app.models.action_prompt import (
    ActionPrompt,
    ActionCategory,
    LocationType,
)
from app.ai.model_selector import UserTier


class TestActionPrompt:
    """Tests for ActionPrompt dataclass."""

    @pytest.fixture
    def free_action(self):
        """Create a free tier action available in towns."""
        return ActionPrompt(
            prompt_id="ask_locals",
            category=ActionCategory.ASK_QUESTION,
            display_text="Ask locals for information",
            description="Talk to NPCs to learn about quests and rumors",
            tier_required=UserTier.FREE,
            context_filter=[LocationType.TOWN, LocationType.TAVERN],
            dm_prompt_template="The player asks locals about {{ topic }}.",
        )

    @pytest.fixture
    def premium_action(self):
        """Create a premium tier action available anywhere."""
        return ActionPrompt(
            prompt_id="investigate",
            category=ActionCategory.GATHER_INFO,
            display_text="Investigate suspicious activity",
            description="Look for clues and hidden details",
            tier_required=UserTier.PREMIUM,
            context_filter=[LocationType.ANY],
            dm_prompt_template="The player investigates the area.",
            icon="magnifying_glass",
        )

    @pytest.fixture
    def elite_action(self):
        """Create an elite tier action for libraries."""
        return ActionPrompt(
            prompt_id="consult_texts",
            category=ActionCategory.SPECIAL,
            display_text="Consult ancient texts",
            description="Study rare manuscripts for hidden knowledge",
            tier_required=UserTier.ELITE,
            context_filter=[LocationType.LIBRARY, LocationType.TOWN],
            dm_prompt_template="The player studies ancient texts.",
            cooldown_turns=3,
        )

    # Availability tests

    def test_free_action_available_to_free_user(self, free_action):
        """Free action should be available to free tier users."""
        assert free_action.is_available(UserTier.FREE, LocationType.TOWN) is True

    def test_free_action_available_to_premium_user(self, free_action):
        """Free action should be available to higher tier users."""
        assert free_action.is_available(UserTier.PREMIUM, LocationType.TOWN) is True
        assert free_action.is_available(UserTier.ELITE, LocationType.TOWN) is True

    def test_premium_action_not_available_to_free_user(self, premium_action):
        """Premium action should not be available to free tier users."""
        assert premium_action.is_available(UserTier.FREE, LocationType.TOWN) is False

    def test_premium_action_available_to_premium_user(self, premium_action):
        """Premium action should be available to premium tier users."""
        assert premium_action.is_available(UserTier.PREMIUM, LocationType.TOWN) is True

    def test_elite_action_not_available_to_premium_user(self, elite_action):
        """Elite action should not be available to premium tier users."""
        assert elite_action.is_available(UserTier.PREMIUM, LocationType.LIBRARY) is False

    def test_elite_action_available_to_elite_user(self, elite_action):
        """Elite action should be available to elite tier users."""
        assert elite_action.is_available(UserTier.ELITE, LocationType.LIBRARY) is True

    # Location filtering tests

    def test_action_available_in_matching_location(self, free_action):
        """Action should be available in matching locations."""
        assert free_action.is_available(UserTier.FREE, LocationType.TOWN) is True
        assert free_action.is_available(UserTier.FREE, LocationType.TAVERN) is True

    def test_action_not_available_in_non_matching_location(self, free_action):
        """Action should not be available in non-matching locations."""
        assert free_action.is_available(UserTier.FREE, LocationType.WILDERNESS) is False
        assert free_action.is_available(UserTier.FREE, LocationType.DUNGEON) is False

    def test_any_location_matches_all(self, premium_action):
        """Action with ANY location should be available everywhere."""
        assert premium_action.is_available(UserTier.PREMIUM, LocationType.TOWN) is True
        assert premium_action.is_available(UserTier.PREMIUM, LocationType.WILDERNESS) is True
        assert premium_action.is_available(UserTier.PREMIUM, LocationType.DUNGEON) is True
        assert premium_action.is_available(UserTier.PREMIUM, LocationType.LIBRARY) is True

    def test_both_tier_and_location_must_match(self, free_action):
        """Both tier and location requirements must be met."""
        # Wrong location, right tier
        assert free_action.is_available(UserTier.ELITE, LocationType.DUNGEON) is False

    # Lock status tests

    def test_is_locked_for_lower_tier(self, premium_action):
        """Action should be locked for lower tier users."""
        assert premium_action.is_locked(UserTier.FREE) is True
        assert premium_action.is_locked(UserTier.BASIC) is True

    def test_is_not_locked_for_sufficient_tier(self, premium_action):
        """Action should not be locked for sufficient tier users."""
        assert premium_action.is_locked(UserTier.PREMIUM) is False
        assert premium_action.is_locked(UserTier.ELITE) is False

    def test_get_lock_reason_returns_message(self, premium_action):
        """Lock reason should explain tier requirement."""
        reason = premium_action.get_lock_reason(UserTier.FREE)
        assert reason is not None
        assert "Premium" in reason

    def test_get_lock_reason_returns_none_when_unlocked(self, premium_action):
        """Lock reason should be None when action is unlocked."""
        reason = premium_action.get_lock_reason(UserTier.PREMIUM)
        assert reason is None

    # Tier hierarchy tests

    def test_tier_hierarchy_free_to_elite(self):
        """Test full tier hierarchy from FREE to ELITE."""
        action = ActionPrompt(
            prompt_id="test",
            category=ActionCategory.EXPLORE,
            display_text="Test",
            description="Test action",
            tier_required=UserTier.BASIC,
            context_filter=[LocationType.ANY],
            dm_prompt_template="Test",
        )

        # FREE < BASIC (should fail)
        assert action.is_available(UserTier.FREE, LocationType.TOWN) is False

        # BASIC >= BASIC (should pass)
        assert action.is_available(UserTier.BASIC, LocationType.TOWN) is True

        # PREMIUM > BASIC (should pass)
        assert action.is_available(UserTier.PREMIUM, LocationType.TOWN) is True

        # ELITE > BASIC (should pass)
        assert action.is_available(UserTier.ELITE, LocationType.TOWN) is True

    # Serialization tests

    def test_to_dict(self, free_action):
        """Test serialization to dictionary."""
        data = free_action.to_dict()

        assert data["prompt_id"] == "ask_locals"
        assert data["category"] == "ask_question"
        assert data["display_text"] == "Ask locals for information"
        assert data["tier_required"] == "free"
        assert data["context_filter"] == ["town", "tavern"]
        assert "dm_prompt_template" in data

    def test_from_dict(self):
        """Test deserialization from dictionary."""
        data = {
            "prompt_id": "explore_area",
            "category": "explore",
            "display_text": "Explore the area",
            "description": "Look around for points of interest",
            "tier_required": "free",
            "context_filter": ["wilderness", "dungeon"],
            "dm_prompt_template": "The player explores {{ location }}.",
            "icon": "compass",
            "cooldown_turns": 2,
        }

        action = ActionPrompt.from_dict(data)

        assert action.prompt_id == "explore_area"
        assert action.category == ActionCategory.EXPLORE
        assert action.tier_required == UserTier.FREE
        assert LocationType.WILDERNESS in action.context_filter
        assert action.icon == "compass"
        assert action.cooldown_turns == 2

    def test_round_trip_serialization(self, free_action):
        """Test that to_dict and from_dict are inverse operations."""
        data = free_action.to_dict()
        restored = ActionPrompt.from_dict(data)

        assert restored.prompt_id == free_action.prompt_id
        assert restored.category == free_action.category
        assert restored.display_text == free_action.display_text
        assert restored.tier_required == free_action.tier_required
        assert restored.context_filter == free_action.context_filter

    def test_from_dict_invalid_category(self):
        """Test error handling for invalid category."""
        data = {
            "prompt_id": "test",
            "category": "invalid_category",
            "display_text": "Test",
            "description": "Test",
            "tier_required": "free",
            "context_filter": ["any"],
            "dm_prompt_template": "Test",
        }

        with pytest.raises(ValueError) as exc_info:
            ActionPrompt.from_dict(data)

        assert "Invalid action category" in str(exc_info.value)

    def test_from_dict_invalid_tier(self):
        """Test error handling for invalid tier."""
        data = {
            "prompt_id": "test",
            "category": "explore",
            "display_text": "Test",
            "description": "Test",
            "tier_required": "super_premium",
            "context_filter": ["any"],
            "dm_prompt_template": "Test",
        }

        with pytest.raises(ValueError) as exc_info:
            ActionPrompt.from_dict(data)

        assert "Invalid user tier" in str(exc_info.value)

    def test_from_dict_invalid_location(self):
        """Test error handling for invalid location type."""
        data = {
            "prompt_id": "test",
            "category": "explore",
            "display_text": "Test",
            "description": "Test",
            "tier_required": "free",
            "context_filter": ["invalid_location"],
            "dm_prompt_template": "Test",
        }

        with pytest.raises(ValueError) as exc_info:
            ActionPrompt.from_dict(data)

        assert "Invalid location type" in str(exc_info.value)

    # Optional fields tests

    def test_optional_icon(self, free_action, premium_action):
        """Test that icon is optional."""
        assert free_action.icon is None
        assert premium_action.icon == "magnifying_glass"

    def test_default_cooldown(self, free_action, elite_action):
        """Test default and custom cooldown values."""
        assert free_action.cooldown_turns == 0
        assert elite_action.cooldown_turns == 3

    # Repr test

    def test_repr(self, free_action):
        """Test string representation."""
        repr_str = repr(free_action)
        assert "ask_locals" in repr_str
        assert "ask_question" in repr_str
        assert "free" in repr_str


class TestActionCategory:
    """Tests for ActionCategory enum."""

    def test_all_categories_defined(self):
        """Verify all expected categories exist."""
        categories = [cat.value for cat in ActionCategory]

        assert "ask_question" in categories
        assert "travel" in categories
        assert "gather_info" in categories
        assert "rest" in categories
        assert "interact" in categories
        assert "explore" in categories
        assert "special" in categories


class TestLocationType:
    """Tests for LocationType enum."""

    def test_all_location_types_defined(self):
        """Verify all expected location types exist."""
        locations = [loc.value for loc in LocationType]

        assert "town" in locations
        assert "tavern" in locations
        assert "wilderness" in locations
        assert "dungeon" in locations
        assert "safe_area" in locations
        assert "library" in locations
        assert "any" in locations
