"""
Tutorial State Tracking System

Manages tutorial progress, onboarding completion, and tooltip tracking for new players.
"""

from datetime import datetime
from functions import get_database_connection
from typing import Callable, Optional
import json
import logging
import re


def validate_tooltip_id(tooltip_id: str) -> bool:
    """Validate tooltip ID contains only safe characters"""
    return bool(re.match(r'^[a-zA-Z0-9_-]+$', tooltip_id))

def initialize_tutorial(player_id: int) -> dict:
    """
    Initialize tutorial tracking for new player
    Returns: {'success': bool, 'tutorial_step': int, 'onboarding_complete': bool}
    """
    # Input validation
    if not isinstance(player_id, int) or player_id <= 0:
        return {'success': False, 'error': 'Invalid player_id: must be positive integer'}

    conn = None
    cursor = None

    try:
        conn = get_database_connection()
        cursor = conn.cursor(dictionary=True)

        # Check if already initialized
        cursor.execute(
            "SELECT * FROM player_tutorial_progress WHERE player_id = %s",
            (player_id,)
        )
        existing = cursor.fetchone()

        if existing:
            return {
                'success': True,
                'tutorial_step': existing['tutorial_step'],
                'onboarding_complete': bool(existing['onboarding_complete'])
            }

        # Initialize
        cursor.execute(
            """INSERT INTO player_tutorial_progress
               (player_id, tutorial_step, onboarding_complete, first_session_date, tooltips_seen)
               VALUES (%s, 0, FALSE, NOW(), '{}')""",
            (player_id,)
        )
        conn.commit()

        logging.info(f"Initialized tutorial for player {player_id}")

        return {
            'success': True,
            'tutorial_step': 0,
            'onboarding_complete': False
        }

    except Exception as e:
        if conn:
            conn.rollback()
        logging.error(f"Error initializing tutorial for player {player_id}: {e}", exc_info=True)
        return {'success': False, 'error': str(e)}

    finally:
        if cursor:
            cursor.close()
        if conn:
            conn.close()

def get_tutorial_progress(player_id: int) -> Optional[dict]:
    """Get current tutorial progress for player"""
    # Input validation
    if not isinstance(player_id, int) or player_id <= 0:
        logging.error(f"Invalid player_id: {player_id}")
        return None

    conn = None
    cursor = None

    try:
        conn = get_database_connection()
        cursor = conn.cursor(dictionary=True)

        cursor.execute(
            "SELECT * FROM player_tutorial_progress WHERE player_id = %s",
            (player_id,)
        )
        result = cursor.fetchone()

        if not result:
            return None

        # Parse JSON tooltips_seen
        tooltips_seen = json.loads(result['tooltips_seen']) if result['tooltips_seen'] else {}

        return {
            'tutorial_step': result['tutorial_step'],
            'onboarding_complete': bool(result['onboarding_complete']),
            'tooltips_seen': tooltips_seen,
            'skip_tutorial': bool(result['skip_tutorial'])
        }

    except Exception as e:
        logging.error(f"Error getting tutorial progress for player {player_id}: {e}", exc_info=True)
        return None

    finally:
        if cursor:
            cursor.close()
        if conn:
            conn.close()

def update_tutorial_step(player_id: int, new_step: int) -> dict:
    """
    Advance player to next tutorial step
    Returns: {'success': bool, 'new_step': int}
    """
    # Input validation
    if not isinstance(player_id, int) or player_id <= 0:
        return {'success': False, 'error': 'Invalid player_id: must be positive integer'}
    if not isinstance(new_step, int) or new_step < 0 or new_step >= 100:
        return {'success': False, 'error': 'Invalid new_step: must be between 0 and 99'}

    conn = None
    cursor = None

    try:
        conn = get_database_connection()
        cursor = conn.cursor()

        cursor.execute(
            "UPDATE player_tutorial_progress SET tutorial_step = %s WHERE player_id = %s",
            (new_step, player_id)
        )

        # Log milestone
        cursor.execute(
            """INSERT INTO tutorial_milestones
               (player_id, milestone_type, milestone_data)
               VALUES (%s, 'step_complete', %s)""",
            (player_id, json.dumps({'step': new_step}))
        )

        conn.commit()

        logging.info(f"Player {player_id} advanced to tutorial step {new_step}")

        return {
            'success': True,
            'new_step': new_step
        }

    except Exception as e:
        if conn:
            conn.rollback()
        logging.error(f"Error updating tutorial step for player {player_id}: {e}", exc_info=True)
        return {'success': False, 'error': str(e)}

    finally:
        if cursor:
            cursor.close()
        if conn:
            conn.close()

def mark_tooltip_seen(player_id: int, tooltip_id: str) -> dict:
    """
    Mark a tooltip as seen by player
    Returns: {'success': bool}
    """
    # Input validation
    if not isinstance(player_id, int) or player_id <= 0:
        return {'success': False, 'error': 'Invalid player_id: must be positive integer'}
    if not isinstance(tooltip_id, str) or not tooltip_id.strip():
        return {'success': False, 'error': 'Invalid tooltip_id: must be non-empty string'}

    # Validate tooltip_id to prevent JSON path injection
    if not validate_tooltip_id(tooltip_id):
        return {'success': False, 'error': f'Invalid tooltip_id format: {tooltip_id}'}

    conn = None
    cursor = None

    try:
        conn = get_database_connection()
        cursor = conn.cursor()

        # Use JSON_SET for atomic update to prevent race conditions
        # JSON_SET will create the path if it doesn't exist
        cursor.execute(
            """UPDATE player_tutorial_progress
               SET tooltips_seen = JSON_SET(
                   COALESCE(tooltips_seen, '{}'),
                   CONCAT('$.', %s),
                   TRUE
               )
               WHERE player_id = %s""",
            (tooltip_id, player_id)
        )

        if cursor.rowcount == 0:
            return {'success': False, 'error': 'Player tutorial not initialized'}

        # Log milestone
        cursor.execute(
            """INSERT INTO tutorial_milestones
               (player_id, milestone_type, milestone_data)
               VALUES (%s, 'tooltip_seen', %s)""",
            (player_id, json.dumps({'tooltip_id': tooltip_id}))
        )

        conn.commit()

        logging.info(f"Player {player_id} saw tooltip: {tooltip_id}")

        return {'success': True}

    except Exception as e:
        if conn:
            conn.rollback()
        logging.error(f"Error marking tooltip for player {player_id}: {e}", exc_info=True)
        return {'success': False, 'error': str(e)}

    finally:
        if cursor:
            cursor.close()
        if conn:
            conn.close()

def complete_onboarding(player_id: int) -> dict:
    """
    Mark onboarding as complete and award achievement
    Returns: {'success': bool, 'reward_diamonds': int}
    """
    # Input validation
    if not isinstance(player_id, int) or player_id <= 0:
        return {'success': False, 'error': 'Invalid player_id: must be positive integer'}

    conn = None
    cursor = None

    try:
        conn = get_database_connection()
        cursor = conn.cursor()

        # Mark complete
        cursor.execute(
            """UPDATE player_tutorial_progress
               SET onboarding_complete = TRUE, onboarding_completed_date = NOW()
               WHERE player_id = %s""",
            (player_id,)
        )

        # Award "First Steps" achievement (25 diamonds) BEFORE commit
        # This allows check_and_unlock to use the same transaction
        from retention.achievements import check_and_unlock
        achievement_result = check_and_unlock(player_id, 'first_steps', cursor, conn)

        # Commit all changes together
        conn.commit()

        logging.info(f"Player {player_id} completed onboarding")

        return {
            'success': True,
            'reward_diamonds': 25
        }

    except Exception as e:
        if conn:
            conn.rollback()
        logging.error(f"Error completing onboarding for player {player_id}: {e}", exc_info=True)
        return {'success': False, 'error': str(e)}

    finally:
        if cursor:
            cursor.close()
        if conn:
            conn.close()

# WebSocket message handlers
def handle_tutorial_step_complete(player_id: int, message_data: dict, send_to_client: Callable) -> None:
    """Handle tutorialStepComplete message from client"""
    step = message_data.get('step')
    result = update_tutorial_step(player_id, step)

    if result['success']:
        send_to_client(player_id, {
            'type': 'tutorialStepUpdated',
            'step': step
        })

def handle_tooltip_seen(player_id: int, message_data: dict) -> None:
    """Handle tooltipSeen message from client"""
    tooltip_id = message_data.get('tooltipId')
    mark_tooltip_seen(player_id, tooltip_id)

def handle_complete_onboarding(player_id: int, message_data: dict, send_to_client: Callable) -> None:
    """Handle completeOnboarding message from client"""
    result = complete_onboarding(player_id)

    if result['success']:
        send_to_client(player_id, {
            'type': 'onboardingComplete',
            'reward': result['reward_diamonds']
        })
