"""
Tests for the GameSession model with solo play support.

Tests cover:
- Solo session creation and serialization
- Multiplayer session creation and serialization
- ConversationEntry with timestamps
- GameState with location_type
- Session type detection
"""

import pytest
from datetime import datetime

from app.models.session import (
    GameSession,
    GameState,
    ConversationEntry,
    SessionConfig,
)
from app.models.enums import (
    SessionStatus,
    SessionType,
    LocationType,
)


class TestGameState:
    """Tests for GameState dataclass."""

    def test_default_values(self):
        """Test GameState has correct defaults."""
        state = GameState()
        assert state.current_location == "Crossroads Village"
        assert state.location_type == LocationType.TOWN
        assert state.discovered_locations == []
        assert state.active_quests == []
        assert state.world_events == []

    def test_to_dict_serializes_location_type(self):
        """Test location_type is serialized as string."""
        state = GameState(
            current_location="The Rusty Anchor",
            location_type=LocationType.TAVERN,
        )
        data = state.to_dict()
        assert data["current_location"] == "The Rusty Anchor"
        assert data["location_type"] == "tavern"

    def test_from_dict_deserializes_location_type(self):
        """Test location_type is deserialized from string."""
        data = {
            "current_location": "Dark Forest",
            "location_type": "wilderness",
            "discovered_locations": ["Town A"],
            "active_quests": ["quest_1"],
            "world_events": [],
        }
        state = GameState.from_dict(data)
        assert state.current_location == "Dark Forest"
        assert state.location_type == LocationType.WILDERNESS
        assert state.discovered_locations == ["Town A"]

    def test_roundtrip_serialization(self):
        """Test GameState serializes and deserializes correctly."""
        state = GameState(
            current_location="Ancient Library",
            location_type=LocationType.LIBRARY,
            discovered_locations=["Town", "Forest"],
            active_quests=["quest_1", "quest_2"],
            world_events=[{"type": "festival"}],
        )
        data = state.to_dict()
        restored = GameState.from_dict(data)

        assert restored.current_location == state.current_location
        assert restored.location_type == state.location_type
        assert restored.discovered_locations == state.discovered_locations
        assert restored.active_quests == state.active_quests


class TestConversationEntry:
    """Tests for ConversationEntry dataclass."""

    def test_auto_timestamp(self):
        """Test timestamp is auto-generated."""
        entry = ConversationEntry(
            turn=1,
            character_id="char_123",
            character_name="Hero",
            action="I explore",
            dm_response="You find a chest",
        )
        assert entry.timestamp
        assert entry.timestamp.endswith("Z")

    def test_provided_timestamp_preserved(self):
        """Test provided timestamp is not overwritten."""
        ts = "2025-11-21T10:30:00Z"
        entry = ConversationEntry(
            turn=1,
            character_id="char_123",
            character_name="Hero",
            action="I explore",
            dm_response="You find a chest",
            timestamp=ts,
        )
        assert entry.timestamp == ts

    def test_to_dict_with_quest_offered(self):
        """Test serialization includes quest_offered when present."""
        entry = ConversationEntry(
            turn=5,
            character_id="char_123",
            character_name="Hero",
            action="Talk to elder",
            dm_response="The elder offers you a quest",
            quest_offered={
                "quest_id": "quest_goblin_cave",
                "quest_name": "Clear the Goblin Cave",
            },
        )
        data = entry.to_dict()
        assert "quest_offered" in data
        assert data["quest_offered"]["quest_id"] == "quest_goblin_cave"

    def test_to_dict_without_quest_offered(self):
        """Test serialization omits quest_offered when None."""
        entry = ConversationEntry(
            turn=1,
            character_id="char_123",
            character_name="Hero",
            action="I explore",
            dm_response="You find nothing",
        )
        data = entry.to_dict()
        assert "quest_offered" not in data

    def test_from_dict_roundtrip(self):
        """Test ConversationEntry roundtrip serialization."""
        entry = ConversationEntry(
            turn=3,
            character_id="char_456",
            character_name="Wizard",
            action="Cast fireball",
            dm_response="The spell illuminates the cave",
            combat_log=[{"action": "attack", "damage": 15}],
            quest_offered={"quest_id": "q1"},
        )
        data = entry.to_dict()
        restored = ConversationEntry.from_dict(data)

        assert restored.turn == entry.turn
        assert restored.character_id == entry.character_id
        assert restored.action == entry.action
        assert restored.dm_response == entry.dm_response
        assert restored.timestamp == entry.timestamp
        assert restored.combat_log == entry.combat_log
        assert restored.quest_offered == entry.quest_offered


class TestGameSessionSolo:
    """Tests for solo GameSession functionality."""

    def test_create_solo_session(self):
        """Test creating a solo session."""
        session = GameSession(
            session_id="sess_123",
            session_type=SessionType.SOLO,
            solo_character_id="char_456",
            user_id="user_789",
        )
        assert session.session_type == SessionType.SOLO
        assert session.solo_character_id == "char_456"
        assert session.user_id == "user_789"
        assert session.is_solo() is True

    def test_is_solo_method(self):
        """Test is_solo returns correct values."""
        solo = GameSession(
            session_id="s1",
            session_type=SessionType.SOLO,
            solo_character_id="c1",
        )
        multi = GameSession(
            session_id="s2",
            session_type=SessionType.MULTIPLAYER,
            party_member_ids=["c1", "c2"],
        )
        assert solo.is_solo() is True
        assert multi.is_solo() is False

    def test_get_character_id_solo(self):
        """Test get_character_id returns solo_character_id for solo sessions."""
        session = GameSession(
            session_id="sess_123",
            session_type=SessionType.SOLO,
            solo_character_id="char_456",
        )
        assert session.get_character_id() == "char_456"

    def test_get_character_id_multiplayer(self):
        """Test get_character_id returns current turn character for multiplayer."""
        session = GameSession(
            session_id="sess_123",
            session_type=SessionType.MULTIPLAYER,
            party_member_ids=["c1", "c2", "c3"],
            turn_order=["c2", "c1", "c3"],
            current_turn=1,
        )
        assert session.get_character_id() == "c1"

    def test_to_dict_includes_new_fields(self):
        """Test to_dict includes session_type, solo_character_id, user_id."""
        session = GameSession(
            session_id="sess_123",
            session_type=SessionType.SOLO,
            solo_character_id="char_456",
            user_id="user_789",
        )
        data = session.to_dict()

        assert data["session_id"] == "sess_123"
        assert data["session_type"] == "solo"
        assert data["solo_character_id"] == "char_456"
        assert data["user_id"] == "user_789"

    def test_from_dict_solo_session(self):
        """Test from_dict correctly deserializes solo session."""
        data = {
            "session_id": "sess_123",
            "session_type": "solo",
            "solo_character_id": "char_456",
            "user_id": "user_789",
            "party_member_ids": [],
            "turn_number": 5,
            "game_state": {
                "current_location": "Town",
                "location_type": "town",
            },
        }
        session = GameSession.from_dict(data)

        assert session.session_id == "sess_123"
        assert session.session_type == SessionType.SOLO
        assert session.solo_character_id == "char_456"
        assert session.user_id == "user_789"
        assert session.turn_number == 5
        assert session.game_state.location_type == LocationType.TOWN

    def test_roundtrip_serialization(self):
        """Test complete roundtrip of solo session."""
        session = GameSession(
            session_id="sess_test",
            session_type=SessionType.SOLO,
            solo_character_id="char_hero",
            user_id="user_player",
            turn_number=10,
            game_state=GameState(
                current_location="Dark Dungeon",
                location_type=LocationType.DUNGEON,
                active_quests=["quest_1"],
            ),
            conversation_history=[
                ConversationEntry(
                    turn=1,
                    character_id="char_hero",
                    character_name="Hero",
                    action="Enter dungeon",
                    dm_response="The darkness swallows you...",
                )
            ],
        )

        data = session.to_dict()
        restored = GameSession.from_dict(data)

        assert restored.session_id == session.session_id
        assert restored.session_type == session.session_type
        assert restored.solo_character_id == session.solo_character_id
        assert restored.user_id == session.user_id
        assert restored.turn_number == session.turn_number
        assert restored.game_state.current_location == session.game_state.current_location
        assert restored.game_state.location_type == session.game_state.location_type
        assert len(restored.conversation_history) == 1
        assert restored.conversation_history[0].action == "Enter dungeon"

    def test_repr_solo(self):
        """Test __repr__ for solo session."""
        session = GameSession(
            session_id="sess_123",
            session_type=SessionType.SOLO,
            solo_character_id="char_456",
            turn_number=5,
        )
        repr_str = repr(session)
        assert "type=solo" in repr_str
        assert "char=char_456" in repr_str
        assert "turn=5" in repr_str

    def test_repr_multiplayer(self):
        """Test __repr__ for multiplayer session."""
        session = GameSession(
            session_id="sess_123",
            session_type=SessionType.MULTIPLAYER,
            party_member_ids=["c1", "c2", "c3"],
            turn_number=10,
        )
        repr_str = repr(session)
        assert "type=multiplayer" in repr_str
        assert "party=3" in repr_str
        assert "turn=10" in repr_str


class TestGameSessionBackwardsCompatibility:
    """Tests for backwards compatibility with existing sessions."""

    def test_default_session_type_is_solo(self):
        """Test new sessions default to solo type."""
        session = GameSession(session_id="test")
        assert session.session_type == SessionType.SOLO

    def test_from_dict_without_session_type(self):
        """Test from_dict handles missing session_type (defaults to solo)."""
        data = {
            "session_id": "old_session",
            "party_member_ids": ["c1"],
        }
        session = GameSession.from_dict(data)
        assert session.session_type == SessionType.SOLO

    def test_from_dict_without_location_type(self):
        """Test from_dict handles missing location_type in game_state."""
        data = {
            "session_id": "old_session",
            "game_state": {
                "current_location": "Old Town",
            },
        }
        session = GameSession.from_dict(data)
        assert session.game_state.location_type == LocationType.TOWN

    def test_existing_methods_still_work(self):
        """Test existing session methods work with new fields."""
        session = GameSession(
            session_id="test",
            session_type=SessionType.SOLO,
            solo_character_id="char_1",
        )

        # Test existing methods
        assert session.is_in_combat() is False
        session.update_activity()
        assert session.last_activity

        # Add conversation entry
        entry = ConversationEntry(
            turn=1,
            character_id="char_1",
            character_name="Hero",
            action="test",
            dm_response="response",
        )
        session.add_conversation_entry(entry)
        assert len(session.conversation_history) == 1


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

    def test_all_location_types_defined(self):
        """Test all expected location types exist."""
        expected = ["town", "tavern", "wilderness", "dungeon", "ruins", "library", "safe_area"]
        actual = [lt.value for lt in LocationType]
        assert sorted(actual) == sorted(expected)

    def test_location_type_from_string(self):
        """Test LocationType can be created from string."""
        assert LocationType("town") == LocationType.TOWN
        assert LocationType("wilderness") == LocationType.WILDERNESS
        assert LocationType("dungeon") == LocationType.DUNGEON


class TestSessionTypeEnum:
    """Tests for SessionType enum."""

    def test_session_types_defined(self):
        """Test session types are defined correctly."""
        assert SessionType.SOLO.value == "solo"
        assert SessionType.MULTIPLAYER.value == "multiplayer"
