"""
Tests for the SessionService.

Tests cover:
- Solo session creation
- Session retrieval and listing
- Conversation history management
- Game state tracking (location, quests, events)
- Session validation and limits
"""

import pytest
from unittest.mock import Mock, patch, MagicMock
from datetime import datetime

from app.services.session_service import (
    SessionService,
    SessionNotFound,
    SessionLimitExceeded,
    SessionValidationError,
    MAX_ACTIVE_SESSIONS,
)
from app.models.session import GameSession, GameState, ConversationEntry
from app.models.enums import SessionStatus, SessionType, LocationType
from app.models.character import Character
from app.models.skills import PlayerClass
from app.models.origins import Origin


@pytest.fixture
def mock_db():
    """Create mock database service."""
    with patch('app.services.session_service.get_database_service') as mock:
        db = Mock()
        mock.return_value = db
        yield db


@pytest.fixture
def mock_appwrite():
    """Create mock Appwrite service."""
    with patch('app.services.session_service.AppwriteService') as mock:
        service = Mock()
        mock.return_value = service
        yield service


@pytest.fixture
def mock_character_service():
    """Create mock character service."""
    with patch('app.services.session_service.get_character_service') as mock:
        service = Mock()
        mock.return_value = service
        yield service


@pytest.fixture
def sample_character():
    """Create a sample character for testing."""
    return Character(
        character_id="char_123",
        user_id="user_456",
        name="Test Hero",
        player_class=Mock(spec=PlayerClass),
        origin=Mock(spec=Origin),
        level=5,
        experience=1000,
        base_stats={"strength": 10},
        unlocked_skills=[],
        inventory=[],
        equipped={},
        gold=100,
        active_quests=[],
        discovered_locations=[],
        current_location="Town"
    )


class TestSessionServiceCreation:
    """Tests for session creation."""

    def test_create_solo_session_success(self, mock_db, mock_appwrite, mock_character_service, sample_character):
        """Test successful solo session creation."""
        mock_character_service.get_character.return_value = sample_character
        mock_db.count_documents.return_value = 0
        mock_db.create_document.return_value = None

        service = SessionService()
        session = service.create_solo_session(
            user_id="user_456",
            character_id="char_123"
        )

        assert session.session_type == SessionType.SOLO
        assert session.solo_character_id == "char_123"
        assert session.user_id == "user_456"
        assert session.turn_number == 0
        assert session.status == SessionStatus.ACTIVE
        assert session.game_state.current_location == "Crossroads Village"
        assert session.game_state.location_type == LocationType.TOWN

        mock_db.create_document.assert_called_once()

    def test_create_solo_session_character_not_found(self, mock_db, mock_appwrite, mock_character_service):
        """Test session creation fails when character not found."""
        from app.services.character_service import CharacterNotFound
        mock_character_service.get_character.side_effect = CharacterNotFound("Not found")

        service = SessionService()
        with pytest.raises(CharacterNotFound):
            service.create_solo_session(
                user_id="user_456",
                character_id="char_invalid"
            )

    def test_create_solo_session_limit_exceeded(self, mock_db, mock_appwrite, mock_character_service, sample_character):
        """Test session creation fails when limit exceeded."""
        mock_character_service.get_character.return_value = sample_character
        mock_db.count_documents.return_value = MAX_ACTIVE_SESSIONS

        service = SessionService()
        with pytest.raises(SessionLimitExceeded):
            service.create_solo_session(
                user_id="user_456",
                character_id="char_123"
            )

    def test_create_solo_session_custom_location(self, mock_db, mock_appwrite, mock_character_service, sample_character):
        """Test session creation with custom starting location."""
        mock_character_service.get_character.return_value = sample_character
        mock_db.count_documents.return_value = 0

        service = SessionService()
        session = service.create_solo_session(
            user_id="user_456",
            character_id="char_123",
            starting_location="Dark Forest",
            starting_location_type=LocationType.WILDERNESS
        )

        assert session.game_state.current_location == "Dark Forest"
        assert session.game_state.location_type == LocationType.WILDERNESS
        assert "Dark Forest" in session.game_state.discovered_locations


class TestSessionServiceRetrieval:
    """Tests for session retrieval."""

    def test_get_session_success(self, mock_db, mock_appwrite, mock_character_service):
        """Test successful session retrieval."""
        session_data = {
            "session_id": "sess_123",
            "session_type": "solo",
            "solo_character_id": "char_456",
            "user_id": "user_789",
            "party_member_ids": [],
            "turn_number": 5,
            "status": "active",
            "game_state": {
                "current_location": "Town",
                "location_type": "town"
            },
            "conversation_history": [],
            "config": {},
            "turn_order": [],
            "current_turn": 0
        }

        mock_document = Mock()
        mock_document.data = {
            'userId': 'user_789',
            'sessionData': __import__('json').dumps(session_data)
        }
        mock_db.get_document.return_value = mock_document

        service = SessionService()
        session = service.get_session("sess_123", "user_789")

        assert session.session_id == "sess_123"
        assert session.user_id == "user_789"
        assert session.turn_number == 5

    def test_get_session_not_found(self, mock_db, mock_appwrite, mock_character_service):
        """Test session retrieval when not found."""
        mock_db.get_document.return_value = None

        service = SessionService()
        with pytest.raises(SessionNotFound):
            service.get_session("sess_invalid")

    def test_get_session_wrong_user(self, mock_db, mock_appwrite, mock_character_service):
        """Test session retrieval with wrong user ID."""
        mock_document = Mock()
        mock_document.data = {
            'userId': 'user_other',
            'sessionData': '{}'
        }
        mock_db.get_document.return_value = mock_document

        service = SessionService()
        with pytest.raises(SessionNotFound):
            service.get_session("sess_123", "user_wrong")

    def test_get_user_sessions(self, mock_db, mock_appwrite, mock_character_service):
        """Test getting all sessions for a user."""
        session_data = {
            "session_id": "sess_1",
            "session_type": "solo",
            "user_id": "user_123",
            "status": "active",
            "turn_number": 0,
            "game_state": {},
            "conversation_history": [],
            "config": {},
            "turn_order": [],
            "current_turn": 0,
            "party_member_ids": []
        }

        mock_doc = Mock()
        mock_doc.data = {'sessionData': __import__('json').dumps(session_data)}
        mock_doc.id = "sess_1"
        mock_db.list_rows.return_value = [mock_doc]

        service = SessionService()
        sessions = service.get_user_sessions("user_123")

        assert len(sessions) == 1
        assert sessions[0].session_id == "sess_1"


class TestConversationHistory:
    """Tests for conversation history management."""

    def test_add_conversation_entry(self, mock_db, mock_appwrite, mock_character_service):
        """Test adding a conversation entry."""
        session_data = {
            "session_id": "sess_123",
            "session_type": "solo",
            "user_id": "user_456",
            "turn_number": 0,
            "status": "active",
            "game_state": {"current_location": "Town", "location_type": "town"},
            "conversation_history": [],
            "config": {},
            "turn_order": [],
            "current_turn": 0,
            "party_member_ids": []
        }

        mock_document = Mock()
        mock_document.data = {
            'userId': 'user_456',
            'sessionData': __import__('json').dumps(session_data)
        }
        mock_db.get_document.return_value = mock_document

        service = SessionService()
        updated = service.add_conversation_entry(
            session_id="sess_123",
            character_id="char_789",
            character_name="Hero",
            action="I explore the area",
            dm_response="You find a hidden path..."
        )

        assert updated.turn_number == 1
        assert len(updated.conversation_history) == 1
        assert updated.conversation_history[0].action == "I explore the area"
        assert updated.conversation_history[0].dm_response == "You find a hidden path..."

        mock_db.update_document.assert_called_once()

    def test_add_conversation_entry_with_quest(self, mock_db, mock_appwrite, mock_character_service):
        """Test adding conversation entry with quest offering."""
        session_data = {
            "session_id": "sess_123",
            "session_type": "solo",
            "user_id": "user_456",
            "turn_number": 5,
            "status": "active",
            "game_state": {},
            "conversation_history": [],
            "config": {},
            "turn_order": [],
            "current_turn": 0,
            "party_member_ids": []
        }

        mock_document = Mock()
        mock_document.data = {
            'userId': 'user_456',
            'sessionData': __import__('json').dumps(session_data)
        }
        mock_db.get_document.return_value = mock_document

        service = SessionService()
        updated = service.add_conversation_entry(
            session_id="sess_123",
            character_id="char_789",
            character_name="Hero",
            action="Talk to elder",
            dm_response="The elder offers you a quest...",
            quest_offered={"quest_id": "quest_goblin", "name": "Clear Goblin Cave"}
        )

        assert updated.conversation_history[0].quest_offered is not None
        assert updated.conversation_history[0].quest_offered["quest_id"] == "quest_goblin"

    def test_get_recent_history(self, mock_db, mock_appwrite, mock_character_service):
        """Test getting recent conversation history."""
        # Create session with 5 conversation entries
        entries = []
        for i in range(5):
            entries.append({
                "turn": i + 1,
                "character_id": "char_123",
                "character_name": "Hero",
                "action": f"Action {i+1}",
                "dm_response": f"Response {i+1}",
                "timestamp": "2025-11-21T10:00:00Z",
                "combat_log": []
            })

        session_data = {
            "session_id": "sess_123",
            "session_type": "solo",
            "user_id": "user_456",
            "turn_number": 5,
            "status": "active",
            "game_state": {},
            "conversation_history": entries,
            "config": {},
            "turn_order": [],
            "current_turn": 0,
            "party_member_ids": []
        }

        mock_document = Mock()
        mock_document.data = {
            'userId': 'user_456',
            'sessionData': __import__('json').dumps(session_data)
        }
        mock_db.get_document.return_value = mock_document

        service = SessionService()
        recent = service.get_recent_history("sess_123", num_turns=3)

        assert len(recent) == 3
        assert recent[0].turn == 3  # Last 3 entries
        assert recent[2].turn == 5


class TestGameStateTracking:
    """Tests for game state tracking methods."""

    def test_update_location(self, mock_db, mock_appwrite, mock_character_service):
        """Test updating session location."""
        session_data = {
            "session_id": "sess_123",
            "session_type": "solo",
            "user_id": "user_456",
            "turn_number": 0,
            "status": "active",
            "game_state": {
                "current_location": "Town",
                "location_type": "town",
                "discovered_locations": ["Town"]
            },
            "conversation_history": [],
            "config": {},
            "turn_order": [],
            "current_turn": 0,
            "party_member_ids": []
        }

        mock_document = Mock()
        mock_document.data = {
            'userId': 'user_456',
            'sessionData': __import__('json').dumps(session_data)
        }
        mock_db.get_document.return_value = mock_document

        service = SessionService()
        updated = service.update_location(
            session_id="sess_123",
            new_location="Dark Forest",
            location_type=LocationType.WILDERNESS
        )

        assert updated.game_state.current_location == "Dark Forest"
        assert updated.game_state.location_type == LocationType.WILDERNESS
        assert "Dark Forest" in updated.game_state.discovered_locations

    def test_add_active_quest(self, mock_db, mock_appwrite, mock_character_service):
        """Test adding an active quest."""
        session_data = {
            "session_id": "sess_123",
            "session_type": "solo",
            "user_id": "user_456",
            "turn_number": 0,
            "status": "active",
            "game_state": {
                "active_quests": [],
                "current_location": "Town",
                "location_type": "town"
            },
            "conversation_history": [],
            "config": {},
            "turn_order": [],
            "current_turn": 0,
            "party_member_ids": []
        }

        mock_document = Mock()
        mock_document.data = {
            'userId': 'user_456',
            'sessionData': __import__('json').dumps(session_data)
        }
        mock_db.get_document.return_value = mock_document

        service = SessionService()
        updated = service.add_active_quest("sess_123", "quest_goblin")

        assert "quest_goblin" in updated.game_state.active_quests

    def test_add_active_quest_limit(self, mock_db, mock_appwrite, mock_character_service):
        """Test adding quest fails when max reached."""
        session_data = {
            "session_id": "sess_123",
            "session_type": "solo",
            "user_id": "user_456",
            "turn_number": 0,
            "status": "active",
            "game_state": {
                "active_quests": ["quest_1", "quest_2"],  # Already at max
                "current_location": "Town",
                "location_type": "town"
            },
            "conversation_history": [],
            "config": {},
            "turn_order": [],
            "current_turn": 0,
            "party_member_ids": []
        }

        mock_document = Mock()
        mock_document.data = {
            'userId': 'user_456',
            'sessionData': __import__('json').dumps(session_data)
        }
        mock_db.get_document.return_value = mock_document

        service = SessionService()
        with pytest.raises(SessionValidationError):
            service.add_active_quest("sess_123", "quest_3")

    def test_remove_active_quest(self, mock_db, mock_appwrite, mock_character_service):
        """Test removing an active quest."""
        session_data = {
            "session_id": "sess_123",
            "session_type": "solo",
            "user_id": "user_456",
            "turn_number": 0,
            "status": "active",
            "game_state": {
                "active_quests": ["quest_1", "quest_2"],
                "current_location": "Town",
                "location_type": "town"
            },
            "conversation_history": [],
            "config": {},
            "turn_order": [],
            "current_turn": 0,
            "party_member_ids": []
        }

        mock_document = Mock()
        mock_document.data = {
            'userId': 'user_456',
            'sessionData': __import__('json').dumps(session_data)
        }
        mock_db.get_document.return_value = mock_document

        service = SessionService()
        updated = service.remove_active_quest("sess_123", "quest_1")

        assert "quest_1" not in updated.game_state.active_quests
        assert "quest_2" in updated.game_state.active_quests

    def test_add_world_event(self, mock_db, mock_appwrite, mock_character_service):
        """Test adding a world event."""
        session_data = {
            "session_id": "sess_123",
            "session_type": "solo",
            "user_id": "user_456",
            "turn_number": 0,
            "status": "active",
            "game_state": {
                "world_events": [],
                "current_location": "Town",
                "location_type": "town"
            },
            "conversation_history": [],
            "config": {},
            "turn_order": [],
            "current_turn": 0,
            "party_member_ids": []
        }

        mock_document = Mock()
        mock_document.data = {
            'userId': 'user_456',
            'sessionData': __import__('json').dumps(session_data)
        }
        mock_db.get_document.return_value = mock_document

        service = SessionService()
        updated = service.add_world_event("sess_123", {"type": "festival", "description": "A festival begins"})

        assert len(updated.game_state.world_events) == 1
        assert updated.game_state.world_events[0]["type"] == "festival"
        assert "timestamp" in updated.game_state.world_events[0]


class TestSessionLifecycle:
    """Tests for session lifecycle management."""

    def test_end_session(self, mock_db, mock_appwrite, mock_character_service):
        """Test ending a session."""
        session_data = {
            "session_id": "sess_123",
            "session_type": "solo",
            "user_id": "user_456",
            "turn_number": 10,
            "status": "active",
            "game_state": {},
            "conversation_history": [],
            "config": {},
            "turn_order": [],
            "current_turn": 0,
            "party_member_ids": []
        }

        mock_document = Mock()
        mock_document.data = {
            'userId': 'user_456',
            'sessionData': __import__('json').dumps(session_data)
        }
        mock_db.get_document.return_value = mock_document

        service = SessionService()
        updated = service.end_session("sess_123", "user_456")

        assert updated.status == SessionStatus.COMPLETED
        mock_db.update_document.assert_called_once()

    def test_count_user_sessions(self, mock_db, mock_appwrite, mock_character_service):
        """Test counting user sessions."""
        mock_db.count_documents.return_value = 3

        service = SessionService()
        count = service.count_user_sessions("user_123", active_only=True)

        assert count == 3
        mock_db.count_documents.assert_called_once()
