"""
Unit tests for tutorial system (ws/retention/tutorial.py).

Tests tutorial state tracking, onboarding completion, and tooltip management.
Tutorial guides new players through initial game mechanics.

Run with: pytest tests/unit/test_tutorial.py -v
"""

import pytest
from unittest.mock import Mock, patch, MagicMock
import sys
from pathlib import Path

# Add ws directory to path
ws_dir = Path(__file__).parent.parent.parent / 'ws'
sys.path.insert(0, str(ws_dir))

# Mock the problematic imports to avoid circular dependencies
sys.modules['functions'] = MagicMock()

from retention.tutorial import (
    initialize_tutorial,
    get_tutorial_progress,
    update_tutorial_step,
    complete_onboarding,
    mark_tooltip_seen,
)


# ============================================================================
# FIXTURES
# ============================================================================

@pytest.fixture
def newborn_player():
    """Create a newborn test player for tutorial tests"""
    player = Mock()
    player.userID = "test_user_tutorial"
    player.id = 99999
    player.c = Mock()
    player.c.ageYears = 0
    player.c.ageDays = 1
    player.c.firstname = "Baby"
    player.c.lastname = "Test"
    return player


@pytest.fixture
def adult_player():
    """Create an adult test player (tutorial already completed)"""
    player = Mock()
    player.userID = "test_user_adult"
    player.id = 12345
    player.c = Mock()
    player.c.ageYears = 25
    return player


@pytest.fixture
def mock_db_connection():
    """Mock database connection and cursor"""
    mock_conn = MagicMock()
    mock_cursor = MagicMock()
    mock_conn.cursor.return_value = mock_cursor

    with patch('retention.tutorial.get_database_connection', return_value=mock_conn):
        yield mock_conn, mock_cursor


# ============================================================================
# TUTORIAL TESTS (8 tests)
# ============================================================================

def test_tutorial_starts_for_new_player(newborn_player, mock_db_connection):
    """Test that tutorial begins for new accounts"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection

    # No existing tutorial record
    mock_cursor.fetchone.return_value = None

    # Act
    result = initialize_tutorial(newborn_player.id)

    # Assert
    assert result['success'] is True
    assert result['tutorial_step'] == 0
    assert result['onboarding_complete'] is False

    # Verify INSERT was called
    insert_calls = [call for call in mock_cursor.execute.call_args_list if 'INSERT' in str(call)]
    assert len(insert_calls) > 0
    mock_conn.commit.assert_called()


def test_tutorial_progresses_through_steps(newborn_player, mock_db_connection):
    """Test that tutorial steps progress in sequence"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection

    # Mock tutorial at step 0
    mock_cursor.fetchone.return_value = {
        'player_id': newborn_player.id,
        'tutorial_step': 0,
        'onboarding_complete': False
    }

    # Act
    result = update_tutorial_step(newborn_player.id, 1)

    # Assert
    assert result['success'] is True
    assert result['tutorial_step'] == 1

    # Verify UPDATE was called
    update_calls = [call for call in mock_cursor.execute.call_args_list if 'UPDATE' in str(call)]
    assert len(update_calls) > 0
    mock_conn.commit.assert_called()


def test_tutorial_completion_tracked(newborn_player, mock_db_connection):
    """Test that tutorial completion is saved"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection

    # Mock tutorial in progress
    mock_cursor.fetchone.return_value = {
        'player_id': newborn_player.id,
        'tutorial_step': 5,
        'onboarding_complete': False
    }

    # Act
    result = complete_onboarding(newborn_player.id)

    # Assert
    assert result['success'] is True
    assert result['onboarding_complete'] is True

    # Verify UPDATE was called to mark complete
    update_calls = [call for call in mock_cursor.execute.call_args_list if 'onboarding_complete' in str(call)]
    assert len(update_calls) > 0
    mock_conn.commit.assert_called()


def test_tutorial_can_be_skipped(newborn_player, mock_db_connection):
    """Test that tutorial skip option works (via complete_onboarding)"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection

    # Mock tutorial at early step
    mock_cursor.fetchone.return_value = {
        'player_id': newborn_player.id,
        'tutorial_step': 1,
        'onboarding_complete': False
    }

    # Act - Complete onboarding acts as "skip tutorial"
    result = complete_onboarding(newborn_player.id)

    # Assert
    assert result['success'] is True
    assert result['onboarding_complete'] is True

    # Verify tutorial was marked complete
    mock_conn.commit.assert_called()


def test_tutorial_step_unlocking(newborn_player, mock_db_connection):
    """Test that steps unlock sequentially"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection

    # Mock tutorial at step 2
    mock_cursor.fetchone.return_value = {
        'player_id': newborn_player.id,
        'tutorial_step': 2,
        'onboarding_complete': False
    }

    # Act - try to advance to step 3
    result = update_tutorial_step(newborn_player.id, 3)

    # Assert
    assert result['success'] is True
    # Steps should progress sequentially
    assert result['tutorial_step'] == 3


def test_tutorial_rewards(newborn_player, mock_db_connection):
    """Test that tutorial steps give rewards"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection

    # Mock tutorial completion
    mock_cursor.fetchone.return_value = {
        'player_id': newborn_player.id,
        'tutorial_step': 5,
        'onboarding_complete': False
    }

    # Act
    result = complete_onboarding(newborn_player.id)

    # Assert
    if result.get('success'):
        # Tutorial completion may award diamonds
        # (Implementation-specific - adjust based on actual behavior)
        assert result['onboarding_complete'] is True


def test_tutorial_only_for_new_players(adult_player, mock_db_connection):
    """Test that existing players don't see tutorial"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection

    # Mock existing player with completed tutorial
    mock_cursor.fetchone.return_value = {
        'player_id': adult_player.id,
        'tutorial_step': 10,
        'onboarding_complete': True
    }

    # Act
    progress = get_tutorial_progress(adult_player.id)

    # Assert
    assert progress is not None
    assert progress['onboarding_complete'] is True
    # Tutorial should not restart for existing players


def test_tutorial_state_persistence(newborn_player, mock_db_connection):
    """Test that tutorial state persists across sessions"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection

    # First session: initialize tutorial
    mock_cursor.fetchone.side_effect = [
        None,  # First call: no existing record
        {  # Second call: fetch after insert
            'player_id': newborn_player.id,
            'tutorial_step': 0,
            'onboarding_complete': False,
            'tooltips_seen': '{}'
        }
    ]

    # Act - Initialize
    init_result = initialize_tutorial(newborn_player.id)

    # Reset side_effect for second call
    mock_cursor.fetchone.side_effect = None
    mock_cursor.fetchone.return_value = {
        'player_id': newborn_player.id,
        'tutorial_step': 0,
        'onboarding_complete': False,
        'tooltips_seen': '{}'
    }

    # Act - Get progress (simulating new session)
    progress = get_tutorial_progress(newborn_player.id)

    # Assert
    assert init_result['success'] is True
    assert progress is not None
    assert progress['tutorial_step'] == 0
    assert progress['onboarding_complete'] is False


# ============================================================================
# TOOLTIP TRACKING TESTS
# ============================================================================

def test_tooltip_tracking(newborn_player, mock_db_connection):
    """Test that tooltips can be marked as seen"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection
    tooltip_id = 'first_activity'

    mock_cursor.fetchone.return_value = {
        'player_id': newborn_player.id,
        'tooltips_seen': '{}'
    }

    # Act
    result = mark_tooltip_seen(newborn_player.id, tooltip_id)

    # Assert
    assert result['success'] is True
    # Verify UPDATE was called
    mock_conn.commit.assert_called()


def test_has_seen_tooltip(newborn_player, mock_db_connection):
    """Test checking if tooltip has been seen"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection
    tooltip_id = 'first_activity'

    # Mock tooltip already seen
    mock_cursor.fetchone.return_value = {
        'player_id': newborn_player.id,
        'tooltips_seen': '{"first_activity": true}'
    }

    # Act - Mark tooltip and verify it was recorded
    result = mark_tooltip_seen(newborn_player.id, tooltip_id)

    # Assert
    assert result['success'] is True


def test_tooltip_not_seen(newborn_player, mock_db_connection):
    """Test checking if tooltip has not been seen"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection
    tooltip_id = 'new_tooltip'

    # Mock empty tooltips
    mock_cursor.fetchone.return_value = {
        'player_id': newborn_player.id,
        'tooltips_seen': '{}'
    }

    # Act - Get progress to check tooltips
    progress = get_tutorial_progress(newborn_player.id)

    # Assert
    assert progress is not None
    # New tooltip should not be in tooltips_seen


# ============================================================================
# VALIDATION TESTS
# ============================================================================

def test_initialize_tutorial_validates_player_id():
    """Test that initialize_tutorial validates player_id"""
    # Act & Assert
    result = initialize_tutorial(-1)
    assert result['success'] is False
    assert 'error' in result

    result = initialize_tutorial(0)
    assert result['success'] is False

    result = initialize_tutorial('invalid')
    assert result['success'] is False


def test_tutorial_already_initialized(newborn_player, mock_db_connection):
    """Test that initializing again returns existing state"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection

    # Mock existing tutorial
    mock_cursor.fetchone.return_value = {
        'player_id': newborn_player.id,
        'tutorial_step': 3,
        'onboarding_complete': False
    }

    # Act
    result = initialize_tutorial(newborn_player.id)

    # Assert
    assert result['success'] is True
    assert result['tutorial_step'] == 3
    # Should not create duplicate record
    insert_calls = [call for call in mock_cursor.execute.call_args_list if 'INSERT' in str(call)]
    assert len(insert_calls) == 0


# ============================================================================
# INTEGRATION TESTS
# ============================================================================

def test_complete_tutorial_flow(newborn_player, mock_db_connection):
    """Test complete tutorial flow from start to finish"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection

    # Mock progression through steps
    steps = [
        {'tutorial_step': 0, 'onboarding_complete': False},
        {'tutorial_step': 1, 'onboarding_complete': False},
        {'tutorial_step': 2, 'onboarding_complete': False},
        {'tutorial_step': 3, 'onboarding_complete': False},
        {'tutorial_step': 4, 'onboarding_complete': False},
        {'tutorial_step': 5, 'onboarding_complete': True},
    ]

    # Act & Assert - Progress through each step
    for i, step_data in enumerate(steps[:-1]):
        mock_cursor.fetchone.return_value = {
            'player_id': newborn_player.id,
            **step_data
        }

        if i < len(steps) - 2:
            result = update_tutorial_step(newborn_player.id, i + 1)
            assert result['success'] is True
        else:
            # Complete tutorial
            result = complete_onboarding(newborn_player.id)
            assert result['success'] is True
            assert result['onboarding_complete'] is True
