"""
Tests for Tutorial State Tracking System

Tests tutorial initialization, progress tracking, and onboarding completion.
"""

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

# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))

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


@pytest.fixture
def mock_db_connection():
    """Mock database connection for tutorial tests."""
    mock_conn = MagicMock()
    mock_cursor = MagicMock()

    # Configure cursor as a dictionary cursor
    mock_cursor.__enter__ = Mock(return_value=mock_cursor)
    mock_cursor.__exit__ = Mock(return_value=None)
    mock_conn.cursor.return_value = mock_cursor

    return mock_conn, mock_cursor


@patch('retention.tutorial.get_database_connection')
def test_initialize_tutorial(mock_get_conn):
    """Test tutorial initialization for new player"""
    player_id = 1

    # Set up mock connection
    mock_conn = MagicMock()
    mock_cursor = MagicMock()
    mock_get_conn.return_value = mock_conn
    mock_conn.cursor.return_value = mock_cursor

    # Mock that player doesn't exist yet (new initialization)
    mock_cursor.fetchone.return_value = None

    # Initialize tutorial
    result = initialize_tutorial(player_id)

    # Verify INSERT was called (new tutorial initialization)
    assert mock_cursor.execute.call_count >= 1
    insert_call = [call for call in mock_cursor.execute.call_args_list if 'INSERT' in str(call)]
    assert len(insert_call) > 0

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


@patch('retention.tutorial.get_database_connection')
def test_get_tutorial_progress(mock_get_conn):
    """Test getting tutorial progress"""
    player_id = 1

    # Set up mock connection
    mock_conn = MagicMock()
    mock_cursor = MagicMock()
    mock_get_conn.return_value = mock_conn
    mock_conn.cursor.return_value = mock_cursor

    # Mock database return value
    mock_cursor.fetchone.return_value = {
        'tutorial_step': 0,
        'onboarding_complete': False,
        'tooltips_seen': '{}',
        'skip_tutorial': False
    }

    progress = get_tutorial_progress(player_id)

    assert progress is not None
    assert progress['tutorial_step'] == 0
    assert progress['tooltips_seen'] == {}
    assert progress['onboarding_complete'] is False


@patch('retention.tutorial.get_database_connection')
def test_update_tutorial_step(mock_get_conn):
    """Test advancing tutorial step"""
    player_id = 1

    # Set up mock connection
    mock_conn = MagicMock()
    mock_cursor = MagicMock()
    mock_get_conn.return_value = mock_conn
    mock_conn.cursor.return_value = mock_cursor

    result = update_tutorial_step(player_id, 1)

    # Verify UPDATE was called
    assert mock_cursor.execute.call_count >= 1
    update_call = [call for call in mock_cursor.execute.call_args_list if 'UPDATE' in str(call)]
    assert len(update_call) > 0

    assert result['success'] is True
    assert result['new_step'] == 1


@patch('retention.tutorial.get_database_connection')
def test_mark_tooltip_seen(mock_get_conn):
    """Test marking tooltip as seen"""
    player_id = 1

    # Set up mock connection
    mock_conn = MagicMock()
    mock_cursor = MagicMock()
    mock_get_conn.return_value = mock_conn
    mock_conn.cursor.return_value = mock_cursor
    mock_cursor.rowcount = 1  # Simulate successful UPDATE

    result = mark_tooltip_seen(player_id, 'energy_bar')

    # Verify UPDATE was called with JSON_SET
    assert mock_cursor.execute.call_count >= 1
    update_call = [call for call in mock_cursor.execute.call_args_list if 'UPDATE' in str(call)]
    assert len(update_call) > 0

    assert result['success'] is True


@patch('retention.achievements.check_and_unlock')
@patch('retention.tutorial.get_database_connection')
def test_complete_onboarding(mock_get_conn, mock_check_unlock):
    """Test completing onboarding"""
    player_id = 1

    # Set up mock connection
    mock_conn = MagicMock()
    mock_cursor = MagicMock()
    mock_get_conn.return_value = mock_conn
    mock_conn.cursor.return_value = mock_cursor

    # Mock achievement unlock
    mock_check_unlock.return_value = {'success': True, 'unlocked': True}

    result = complete_onboarding(player_id)

    # Verify UPDATE was called
    assert mock_cursor.execute.call_count >= 1
    update_call = [call for call in mock_cursor.execute.call_args_list if 'UPDATE' in str(call)]
    assert len(update_call) > 0

    assert result['success'] is True
    assert result['reward_diamonds'] == 25  # First Steps achievement


# Additional test for idempotency
@patch('retention.tutorial.get_database_connection')
def test_initialize_tutorial_idempotent(mock_get_conn):
    """Test that initializing tutorial twice doesn't create duplicate records"""
    player_id = 2

    # Set up mock connection
    mock_conn = MagicMock()
    mock_cursor = MagicMock()
    mock_get_conn.return_value = mock_conn
    mock_conn.cursor.return_value = mock_cursor

    # First call: no existing record
    mock_cursor.fetchone.return_value = None
    result1 = initialize_tutorial(player_id)
    assert result1['success'] is True

    # Reset mock for second call
    mock_cursor.reset_mock()

    # Second call: existing record found
    mock_cursor.fetchone.return_value = {
        'tutorial_step': 0,
        'onboarding_complete': False
    }
    result2 = initialize_tutorial(player_id)
    assert result2['success'] is True
    assert result2['tutorial_step'] == 0

    # Verify no INSERT on second call (should return existing data)
    insert_calls = [call for call in mock_cursor.execute.call_args_list if 'INSERT' in str(call)]
    assert len(insert_calls) == 0


@patch('retention.tutorial.get_database_connection')
def test_multiple_tooltips(mock_get_conn):
    """Test marking multiple tooltips as seen"""
    player_id = 3

    # Set up mock connection
    mock_conn = MagicMock()
    mock_cursor = MagicMock()
    mock_get_conn.return_value = mock_conn
    mock_conn.cursor.return_value = mock_cursor
    mock_cursor.rowcount = 1  # Simulate successful UPDATEs

    # Mark multiple tooltips
    result1 = mark_tooltip_seen(player_id, 'tooltip1')
    assert result1['success'] is True

    result2 = mark_tooltip_seen(player_id, 'tooltip2')
    assert result2['success'] is True

    result3 = mark_tooltip_seen(player_id, 'tooltip3')
    assert result3['success'] is True

    # Verify 3 UPDATE calls were made
    update_calls = [call for call in mock_cursor.execute.call_args_list if 'UPDATE' in str(call)]
    assert len(update_calls) >= 3


@patch('retention.tutorial.get_database_connection')
def test_tutorial_step_progression(mock_get_conn):
    """Test advancing through multiple tutorial steps"""
    player_id = 4

    # Set up mock connection
    mock_conn = MagicMock()
    mock_cursor = MagicMock()
    mock_get_conn.return_value = mock_conn
    mock_conn.cursor.return_value = mock_cursor

    # Progress through steps
    for step in range(1, 6):
        result = update_tutorial_step(player_id, step)
        assert result['success'] is True
        assert result['new_step'] == step

    # Verify 5 UPDATE calls were made (one for each step)
    update_calls = [call for call in mock_cursor.execute.call_args_list if 'UPDATE' in str(call)]
    assert len(update_calls) >= 5
