#!/usr/bin/env python3
"""
Mice Game Score API
FastAPI application with SQLite database for user and score management
"""

from fastapi import FastAPI, HTTPException, Depends
from fastapi.responses import JSONResponse
import sqlite3
import os
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel
import hashlib
import uuid


# Pydantic models for request/response
class User(BaseModel):
    user_id: str
    device_id: str
    created_at: Optional[str] = None
    last_active: Optional[str] = None

class Score(BaseModel):
    user_id: str
    device_id: str
    score: int
    game_completed: bool = True
    timestamp: Optional[str] = None

class ScoreResponse(BaseModel):
    id: int
    user_id: str
    device_id: str
    score: int
    game_completed: bool
    timestamp: str

class UserResponse(BaseModel):
    user_id: str
    device_id: str
    created_at: str
    last_active: str
    total_scores: int
    best_score: int


# Database setup
DATABASE_FILE = "mice_game.db"

def init_database():
    """Initialize the SQLite database with required tables"""
    conn = sqlite3.connect(DATABASE_FILE)
    cursor = conn.cursor()
    
    # Create users table
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id TEXT NOT NULL,
            device_id TEXT NOT NULL,
            created_at TEXT NOT NULL,
            last_active TEXT NOT NULL,
            UNIQUE(user_id, device_id)
        )
    ''')
    
    # Create scores table
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS scores (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id TEXT NOT NULL,
            device_id TEXT NOT NULL,
            score INTEGER NOT NULL,
            game_completed BOOLEAN NOT NULL DEFAULT 1,
            timestamp TEXT NOT NULL,
            FOREIGN KEY (user_id, device_id) REFERENCES users (user_id, device_id)
        )
    ''')
    
    # Create indexes for better performance
    cursor.execute('CREATE INDEX IF NOT EXISTS idx_users_device ON users(device_id)')
    cursor.execute('CREATE INDEX IF NOT EXISTS idx_scores_user_device ON scores(user_id, device_id)')
    cursor.execute('CREATE INDEX IF NOT EXISTS idx_scores_timestamp ON scores(timestamp)')
    
    conn.commit()
    conn.close()

def get_db_connection():
    """Get database connection"""
    if not os.path.exists(DATABASE_FILE):
        init_database()
    
    conn = sqlite3.connect(DATABASE_FILE)
    conn.row_factory = sqlite3.Row  # Enable column access by name
    return conn

def close_db_connection(conn):
    """Close database connection"""
    conn.close()


# FastAPI app
app = FastAPI(
    title="Mice Game Score API",
    description="API for managing users and scores in the Mice game",
    version="1.0.0"
)

# Initialize database on startup
@app.on_event("startup")
def startup_event():
    init_database()


# Utility functions
def validate_device_id(device_id: str) -> bool:
    """Validate device ID format"""
    return device_id.startswith("DEV-") and len(device_id) == 12

def validate_user_id(user_id: str) -> bool:
    """Validate user ID format"""
    return len(user_id.strip()) > 0 and len(user_id) <= 50

def user_exists(conn, user_id: str, device_id: str) -> bool:
    """Check if user exists in database"""
    cursor = conn.cursor()
    cursor.execute(
        "SELECT 1 FROM users WHERE user_id = ? AND device_id = ?",
        (user_id, device_id)
    )
    return cursor.fetchone() is not None

def user_id_unique_globally(conn, user_id: str) -> bool:
    """Check if user_id is globally unique (across all devices)"""
    cursor = conn.cursor()
    cursor.execute("SELECT 1 FROM users WHERE user_id = ?", (user_id,))
    return cursor.fetchone() is None


# API Endpoints

@app.get("/")
def read_root():
    """API root endpoint"""
    return {
        "message": "Mice Game Score API",
        "version": "1.0.0",
        "endpoints": [
            "GET /users/{device_id} - Get all users for device",
            "POST /signup/{device_id}/{user_id} - Register new user",
            "POST /score/{device_id}/{user_id} - Submit score"
        ]
    }

@app.post("/signup/{device_id}/{user_id}")
def signup_user(device_id: str, user_id: str):
    """
    Register a new user for a device
    - device_id: Device identifier (format: DEV-XXXXXXXX)
    - user_id: Unique user identifier
    """
    # Validate inputs
    if not validate_device_id(device_id):
        raise HTTPException(
            status_code=400, 
            detail="Invalid device_id format. Must be 'DEV-XXXXXXXX'"
        )
    
    if not validate_user_id(user_id):
        raise HTTPException(
            status_code=400,
            detail="Invalid user_id. Must be 1-50 characters long"
        )
    
    conn = get_db_connection()
    
    try:
        # Check if user_id is globally unique
        if not user_id_unique_globally(conn, user_id):
            raise HTTPException(
                status_code=409,
                detail=f"User ID '{user_id}' already exists. Choose a different user ID."
            )
        
        # Check if user already exists for this device
        if user_exists(conn, user_id, device_id):
            raise HTTPException(
                status_code=409,
                detail=f"User '{user_id}' already registered for device '{device_id}'"
            )
        
        # Register the user
        cursor = conn.cursor()
        now = datetime.now().isoformat()
        
        cursor.execute(
            "INSERT INTO users (user_id, device_id, created_at, last_active) VALUES (?, ?, ?, ?)",
            (user_id, device_id, now, now)
        )
        
        conn.commit()
        
        return {
            "success": True,
            "message": f"User '{user_id}' successfully registered for device '{device_id}'",
            "user": {
                "user_id": user_id,
                "device_id": device_id,
                "created_at": now
            }
        }
        
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
    finally:
        close_db_connection(conn)

@app.post("/score/{device_id}/{user_id}")
def submit_score(device_id: str, user_id: str, score_data: Score):
    """
    Submit a score for a registered user
    - device_id: Device identifier
    - user_id: User identifier
    - score_data: Score information (score, game_completed)
    """
    # Validate inputs
    if not validate_device_id(device_id):
        raise HTTPException(
            status_code=400,
            detail="Invalid device_id format. Must be 'DEV-XXXXXXXX'"
        )
    
    if not validate_user_id(user_id):
        raise HTTPException(
            status_code=400,
            detail="Invalid user_id"
        )
    
    if score_data.score < 0:
        raise HTTPException(
            status_code=400,
            detail="Score must be non-negative"
        )
    
    conn = get_db_connection()
    
    try:
        # Check if user exists and is registered for this device
        if not user_exists(conn, user_id, device_id):
            raise HTTPException(
                status_code=404,
                detail=f"User '{user_id}' not registered for device '{device_id}'. Please signup first."
            )
        
        # Add the score
        cursor = conn.cursor()
        timestamp = datetime.now().isoformat()
        
        cursor.execute(
            "INSERT INTO scores (user_id, device_id, score, game_completed, timestamp) VALUES (?, ?, ?, ?, ?)",
            (user_id, device_id, score_data.score, score_data.game_completed, timestamp)
        )
        
        # Update user's last_active timestamp
        cursor.execute(
            "UPDATE users SET last_active = ? WHERE user_id = ? AND device_id = ?",
            (timestamp, user_id, device_id)
        )
        
        conn.commit()
        
        # Get user's statistics
        cursor.execute(
            """
            SELECT 
                COUNT(*) as total_games,
                MAX(score) as best_score,
                AVG(score) as avg_score,
                SUM(score) as total_score
            FROM scores 
            WHERE user_id = ? AND device_id = ?
            """,
            (user_id, device_id)
        )
        
        stats = cursor.fetchone()
        
        return {
            "success": True,
            "message": f"Score {score_data.score} submitted successfully",
            "score_id": cursor.lastrowid,
            "user_stats": {
                "user_id": user_id,
                "device_id": device_id,
                "total_games": stats["total_games"],
                "best_score": stats["best_score"],
                "average_score": round(stats["avg_score"], 2) if stats["avg_score"] else 0,
                "total_score": stats["total_score"]
            }
        }
        
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
    finally:
        close_db_connection(conn)

@app.get("/users/{device_id}")
def get_device_users(device_id: str) -> List[UserResponse]:
    """
    Get all registered users for a specific device
    - device_id: Device identifier
    """
    # Validate device_id
    if not validate_device_id(device_id):
        raise HTTPException(
            status_code=400,
            detail="Invalid device_id format. Must be 'DEV-XXXXXXXX'"
        )
    
    conn = get_db_connection()
    
    try:
        cursor = conn.cursor()
        
        # Get users with their statistics
        cursor.execute(
            """
            SELECT 
                u.user_id,
                u.device_id,
                u.created_at,
                u.last_active,
                COUNT(s.id) as total_scores,
                COALESCE(MAX(s.score), 0) as best_score
            FROM users u
            LEFT JOIN scores s ON u.user_id = s.user_id AND u.device_id = s.device_id
            WHERE u.device_id = ?
            GROUP BY u.user_id, u.device_id, u.created_at, u.last_active
            ORDER BY u.last_active DESC
            """,
            (device_id,)
        )
        
        users = cursor.fetchall()
        
        if not users:
            return []
        
        return [
            UserResponse(
                user_id=user["user_id"],
                device_id=user["device_id"],
                created_at=user["created_at"],
                last_active=user["last_active"],
                total_scores=user["total_scores"],
                best_score=user["best_score"]
            )
            for user in users
        ]
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
    finally:
        close_db_connection(conn)

# Additional useful endpoints

@app.get("/scores/{device_id}/{user_id}")
def get_user_scores(device_id: str, user_id: str, limit: int = 10) -> List[ScoreResponse]:
    """
    Get recent scores for a user
    - device_id: Device identifier
    - user_id: User identifier
    - limit: Maximum number of scores to return (default: 10)
    """
    if not validate_device_id(device_id) or not validate_user_id(user_id):
        raise HTTPException(status_code=400, detail="Invalid device_id or user_id")
    
    if limit < 1 or limit > 100:
        raise HTTPException(status_code=400, detail="Limit must be between 1 and 100")
    
    conn = get_db_connection()
    
    try:
        # Check if user exists
        if not user_exists(conn, user_id, device_id):
            raise HTTPException(
                status_code=404,
                detail=f"User '{user_id}' not found for device '{device_id}'"
            )
        
        cursor = conn.cursor()
        cursor.execute(
            """
            SELECT id, user_id, device_id, score, game_completed, timestamp
            FROM scores 
            WHERE user_id = ? AND device_id = ?
            ORDER BY timestamp DESC
            LIMIT ?
            """,
            (user_id, device_id, limit)
        )
        
        scores = cursor.fetchall()
        
        return [
            ScoreResponse(
                id=score["id"],
                user_id=score["user_id"],
                device_id=score["device_id"],
                score=score["score"],
                game_completed=bool(score["game_completed"]),
                timestamp=score["timestamp"]
            )
            for score in scores
        ]
        
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
    finally:
        close_db_connection(conn)

@app.get("/leaderboard/{device_id}")
def get_device_leaderboard(device_id: str, limit: int = 10):
    """
    Get leaderboard for a device (top scores)
    - device_id: Device identifier
    - limit: Maximum number of entries to return (default: 10)
    """
    if not validate_device_id(device_id):
        raise HTTPException(status_code=400, detail="Invalid device_id")
    
    if limit < 1 or limit > 100:
        raise HTTPException(status_code=400, detail="Limit must be between 1 and 100")
    
    conn = get_db_connection()
    
    try:
        cursor = conn.cursor()
        cursor.execute(
            """
            SELECT 
                user_id,
                MAX(score) as best_score,
                COUNT(*) as total_games,
                MAX(timestamp) as last_play
            FROM scores 
            WHERE device_id = ?
            GROUP BY user_id
            ORDER BY best_score DESC, last_play DESC
            LIMIT ?
            """,
            (device_id, limit)
        )
        
        leaderboard = cursor.fetchall()
        
        return [
            {
                "rank": idx + 1,
                "user_id": entry["user_id"],
                "best_score": entry["best_score"],
                "total_games": entry["total_games"],
                "last_play": entry["last_play"]
            }
            for idx, entry in enumerate(leaderboard)
        ]
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
    finally:
        close_db_connection(conn)


@app.get("/leaderboard/global/top")
def get_global_leaderboard(limit: int = 10):
    """
    Get global leaderboard across all devices (absolute top scores)
    - limit: Maximum number of entries to return (default: 10)
    """
    if limit < 1 or limit > 100:
        raise HTTPException(status_code=400, detail="Limit must be between 1 and 100")
    
    conn = get_db_connection()
    
    try:
        cursor = conn.cursor()
        cursor.execute(
            """
            SELECT 
                user_id,
                device_id,
                MAX(score) as best_score,
                COUNT(*) as total_games,
                MAX(timestamp) as last_play
            FROM scores 
            GROUP BY user_id, device_id
            ORDER BY best_score DESC, last_play DESC
            LIMIT ?
            """,
            (limit,)
        )
        
        leaderboard = cursor.fetchall()
        
        return [
            {
                "rank": idx + 1,
                "user_id": entry["user_id"],
                "device_id": entry["device_id"],
                "best_score": entry["best_score"],
                "total_games": entry["total_games"],
                "last_play": entry["last_play"]
            }
            for idx, entry in enumerate(leaderboard)
        ]
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
    finally:
        close_db_connection(conn)


if __name__ == "__main__":
    import uvicorn
    
    print("Starting Mice Game Score API...")
    print("Available endpoints:")
    print("  POST /signup/{device_id}/{user_id} - Register new user")
    print("  POST /score/{device_id}/{user_id} - Submit score")
    print("  GET /users/{device_id} - Get all users for device")
    print("  GET /scores/{device_id}/{user_id} - Get user scores")
    print("  GET /leaderboard/{device_id} - Get device leaderboard")
    print()
    
    # Initialize database
    init_database()
    
    uvicorn.run(app, host="0.0.0.0", port=8000)
