#!/usr/bin/env python3
"""
Direct Radarr Database Client for NFOGuard
Provides high-performance access to Radarr's SQLite/PostgreSQL database
"""

import os
import sqlite3
import psycopg2
import psycopg2.extras
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple, Union
from urllib.parse import urlparse

from core.logging import _log


class RadarrDbClient:
    """Direct database client for Radarr's SQLite or PostgreSQL database"""
    
    def __init__(self, 
                 db_type: str = "sqlite",
                 db_path: Optional[str] = None, 
                 db_host: Optional[str] = None,
                 db_port: Optional[int] = None,
                 db_name: Optional[str] = None,
                 db_user: Optional[str] = None,
                 db_password: Optional[str] = None):
        """
        Initialize Radarr database client
        
        Args:
            db_type: "sqlite" or "postgresql"
            db_path: Path to SQLite database file
            db_host: PostgreSQL host
            db_port: PostgreSQL port  
            db_name: PostgreSQL database name
            db_user: PostgreSQL username
            db_password: PostgreSQL password
        """
        self.db_type = db_type.lower()
        self.db_path = db_path
        self.db_host = db_host
        self.db_port = db_port or 5432
        self.db_name = db_name
        self.db_user = db_user
        self.db_password = db_password
        
        self._test_connection()
        
    @classmethod
    def from_env(cls) -> Optional['RadarrDbClient']:
        """Create client from environment variables"""
        db_type = os.environ.get("RADARR_DB_TYPE", "").lower()
        
        if not db_type:
            return None
            
        if db_type == "sqlite":
            db_path = os.environ.get("RADARR_DB_PATH")
            if not db_path or not Path(db_path).exists():
                _log("WARNING", f"RADARR_DB_PATH not found or invalid: {db_path}")
                return None
            return cls(db_type="sqlite", db_path=db_path)
            
        elif db_type == "postgresql":
            # Support both individual vars and connection string
            db_url = os.environ.get("RADARR_DB_URL")
            if db_url:
                parsed = urlparse(db_url)
                return cls(
                    db_type="postgresql",
                    db_host=parsed.hostname,
                    db_port=parsed.port or 5432,
                    db_name=parsed.path.lstrip('/'),
                    db_user=parsed.username,
                    db_password=parsed.password
                )
            else:
                return cls(
                    db_type="postgresql",
                    db_host=os.environ.get("RADARR_DB_HOST"),
                    db_port=int(os.environ.get("RADARR_DB_PORT", "5432")),
                    db_name=os.environ.get("RADARR_DB_NAME"),
                    db_user=os.environ.get("RADARR_DB_USER"),
                    db_password=os.environ.get("RADARR_DB_PASSWORD")
                )
        else:
            _log("ERROR", f"Unsupported database type: {db_type}")
            return None
    
    def _test_connection(self) -> None:
        """Test database connection on initialization"""
        try:
            conn = self._get_connection()
            if conn:
                conn.close()
                _log("INFO", f"Connected to Radarr {self.db_type} database successfully")
            else:
                raise Exception("Failed to create connection")
        except Exception as e:
            _log("ERROR", f"Failed to connect to Radarr database: {e}")
            raise
    
    def _get_connection(self) -> Union[sqlite3.Connection, psycopg2.extensions.connection]:
        """Get database connection"""
        if self.db_type == "sqlite":
            conn = sqlite3.connect(self.db_path)
            conn.row_factory = sqlite3.Row
            return conn
        elif self.db_type == "postgresql":
            conn = psycopg2.connect(
                host=self.db_host,
                port=self.db_port,
                database=self.db_name,
                user=self.db_user,
                password=self.db_password
            )
            return conn
        else:
            raise ValueError(f"Unsupported database type: {self.db_type}")
    
    def get_movie_by_imdb(self, imdb_id: str) -> Optional[Dict[str, Any]]:
        """
        Find movie by IMDb ID using database query
        
        Returns:
            Dictionary with movie info including id, imdbId, title, year, path
        """
        imdb_id = imdb_id if imdb_id.startswith("tt") else f"tt{imdb_id}"
        
        query = """
        SELECT 
            m."Id" as id,
            m."Path" as path,
            m."Added" as added,
            mm."ImdbId" as imdb_id,
            mm."Title" as title,
            mm."Year" as year,
            mm."DigitalRelease" as digital_release
        FROM "Movies" m
        JOIN "MovieMetadata" mm ON m."MovieMetadataId" = mm."Id"
        WHERE mm."ImdbId" = %s
        """
        
        if self.db_type == "sqlite":
            query = query.replace("%s", "?")
        
        try:
            with self._get_connection() as conn:
                if self.db_type == "postgresql":
                    cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
                else:
                    cursor = conn.cursor()
                    
                cursor.execute(query, (imdb_id,))
                row = cursor.fetchone()
                
                if row:
                    return dict(row) if self.db_type == "sqlite" else row
                    
        except Exception as e:
            _log("ERROR", f"Database query error for IMDb {imdb_id}: {e}")
            
        return None
    
    def get_earliest_import_date(self, movie_id: int) -> Tuple[Optional[str], str]:
        """
        Get earliest import date from History table, accounting for upgrade scenarios
        
        Args:
            movie_id: Radarr movie ID
            
        Returns:
            (date_iso, source_description)
        """
        # If first event is rename, all subsequent imports are upgrades - skip them
        if self.is_first_event_rename_based(movie_id):
            _log("INFO", f"Movie {movie_id} has rename-first history - all imports are upgrades, skipping")
            return None, "radarr:db.upgrade_imports_skipped"
        
        # Query for earliest import event - PostgreSQL uses INTEGER EventType (3 = import)
        import_query = """
        SELECT 
            h."Date" as event_date,
            h."Data" as event_data,
            h."EventType" as event_type
        FROM "History" h
        WHERE h."MovieId" = %s 
            AND h."EventType" = 3
        ORDER BY h."Date" ASC
        LIMIT 1
        """
        
        # Fallback: earliest grab event - PostgreSQL uses INTEGER EventType (1 = grab)
        grab_query = """
        SELECT 
            h."Date" as event_date,
            h."Data" as event_data,
            h."EventType" as event_type
        FROM "History" h
        WHERE h."MovieId" = %s 
            AND h."EventType" = 1
            AND h."Data" IS NOT NULL
        ORDER BY h."Date" ASC
        LIMIT 1
        """
        
        if self.db_type == "sqlite":
            import_query = import_query.replace("%s", "?")
            grab_query = grab_query.replace("%s", "?")
        
        try:
            with self._get_connection() as conn:
                if self.db_type == "postgresql":
                    cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
                else:
                    cursor = conn.cursor()
                
                # Try import events first
                cursor.execute(import_query, (movie_id,))
                row = cursor.fetchone()
                
                if row:
                    event_date = row['event_date'] if self.db_type == "postgresql" else row[0]
                    event_type = row['event_type'] if self.db_type == "postgresql" else row[2]
                    if isinstance(event_date, str):
                        dt = datetime.fromisoformat(event_date.replace("Z", "+00:00"))
                    else:
                        dt = event_date.replace(tzinfo=timezone.utc)
                    
                    date_iso = dt.astimezone(timezone.utc).isoformat(timespec="seconds")
                    _log("INFO", f"✅ Found import event ({event_type}) for movie {movie_id} at {date_iso}")
                    return date_iso, "radarr:db.history.import"
                
                # Fallback to grab events
                cursor.execute(grab_query, (movie_id,))
                row = cursor.fetchone()
                
                if row:
                    event_date = row['event_date'] if self.db_type == "postgresql" else row[0]
                    event_type = row['event_type'] if self.db_type == "postgresql" else row[2]
                    if isinstance(event_date, str):
                        dt = datetime.fromisoformat(event_date.replace("Z", "+00:00"))
                    else:
                        dt = event_date.replace(tzinfo=timezone.utc)
                    
                    date_iso = dt.astimezone(timezone.utc).isoformat(timespec="seconds")
                    _log("WARNING", f"⚠️ Using grab event ({event_type}) for movie {movie_id} at {date_iso}")
                    return date_iso, "radarr:db.history.grab"
                    
        except Exception as e:
            _log("ERROR", f"Database query error for movie {movie_id}: {e}")
            
        return None, "radarr:db.no_date_found"

    def is_first_event_rename_based(self, movie_id: int) -> bool:
        """
        Check if the first event in history is rename-based (not a true import)
        
        This helps identify movies where:
        - First event: movieFileRenamed (EventType = 8)
        - Followed by: downloadFolderImported (EventType = 3) - this is an upgrade
        
        In such cases, we should prefer release dates over the upgrade date
        """
        query = """
        SELECT h."EventType" as event_type, h."Date" as event_date
        FROM "History" h
        WHERE h."MovieId" = %s
        ORDER BY h."Date" ASC
        LIMIT 5
        """
        
        if self.db_type == "sqlite":
            query = query.replace("%s", "?")
        
        try:
            with self._get_connection() as conn:
                if self.db_type == "postgresql":
                    cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
                else:
                    cursor = conn.cursor()
                
                cursor.execute(query, (movie_id,))
                rows = cursor.fetchall()
                
                if rows:
                    _log("INFO", f"Movie {movie_id} history debug - first 5 events:")
                    for i, row in enumerate(rows):
                        event_type = row['event_type'] if self.db_type == "postgresql" else row[0] 
                        event_date = row['event_date'] if self.db_type == "postgresql" else row[1]
                        _log("INFO", f"  Event {i+1}: Type={event_type}, Date={event_date}")
                    
                    first_event_type = rows[0]['event_type'] if self.db_type == "postgresql" else rows[0][0]
                    # EventType 8 = movieFileRenamed
                    # Also check for EventType 7 = movieFileRenamed in some Radarr versions
                    is_rename_first = first_event_type in [7, 8]
                    _log("INFO", f"Movie {movie_id}: First event type={first_event_type}, is_rename_first={is_rename_first}")
                    
                    if is_rename_first:
                        _log("INFO", f"🎯 Movie {movie_id} detected as rename-first scenario - will prefer release dates over import dates")
                    
                    return is_rename_first
                else:
                    _log("WARNING", f"Movie {movie_id}: No history events found - this could indicate missing data")
                    
        except Exception as e:
            _log("ERROR", f"Error checking first event type for movie {movie_id}: {e}")
            
        return False
    
    def get_movie_file_date(self, movie_id: int) -> Optional[str]:
        """
        Get earliest file dateAdded as fallback
        
        Args:
            movie_id: Radarr movie ID
            
        Returns:
            ISO date string or None
        """
        query = """
        SELECT MIN(mf."DateAdded") as earliest_date
        FROM "MovieFiles" mf
        WHERE mf."MovieId" = %s
        """
        
        if self.db_type == "sqlite":
            query = query.replace("%s", "?")
        
        try:
            with self._get_connection() as conn:
                if self.db_type == "postgresql":
                    cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
                else:
                    cursor = conn.cursor()
                    
                cursor.execute(query, (movie_id,))
                row = cursor.fetchone()
                
                if row:
                    date_value = row['earliest_date'] if self.db_type == "postgresql" else row[0]
                    if date_value:
                        if isinstance(date_value, str):
                            dt = datetime.fromisoformat(date_value.replace("Z", "+00:00"))
                        else:
                            dt = date_value.replace(tzinfo=timezone.utc)
                        
                        return dt.astimezone(timezone.utc).isoformat(timespec="seconds")
                        
        except Exception as e:
            _log("ERROR", f"Database query error for movie file date {movie_id}: {e}")
            
        return None
    
    def get_movie_import_date_optimized(self, movie_id: int, fallback_to_file_date: bool = True) -> Tuple[Optional[str], str]:
        """
        Get the best import date for a movie using optimized database queries
        
        Args:
            movie_id: Radarr movie ID
            fallback_to_file_date: Whether to fall back to file dateAdded
            
        Returns:
            (date_iso, source_description)
        """
        # Try history first - this handles upgrade detection internally
        date_iso, source = self.get_earliest_import_date(movie_id)
        if date_iso:
            return date_iso, source
        
        # Check if we skipped upgrades and should prefer release dates
        if source == "radarr:db.upgrade_imports_skipped":
            _log("INFO", f"Movie {movie_id} upgrade scenario detected - signaling to prefer release dates")
            return None, "radarr:db.prefer_release_dates"
        
        # Fallback to file date if requested
        if fallback_to_file_date:
            file_date = self.get_movie_file_date(movie_id)
            if file_date:
                _log("WARNING", f"Using file dateAdded as fallback for movie_id {movie_id}")
                return file_date, "radarr:db.file.dateAdded"
        
        return None, "radarr:db.no_date_found"
    
    def bulk_import_dates(self, imdb_ids: List[str]) -> Dict[str, Tuple[Optional[str], str]]:
        """
        Get import dates for multiple movies in a single query
        
        Args:
            imdb_ids: List of IMDb IDs
            
        Returns:
            Dictionary mapping imdb_id -> (date_iso, source)
        """
        if not imdb_ids:
            return {}
        
        # Ensure all IMDb IDs have tt prefix
        clean_imdb_ids = [imdb_id if imdb_id.startswith("tt") else f"tt{imdb_id}" for imdb_id in imdb_ids]
        
        placeholders = ",".join(["%s"] * len(clean_imdb_ids))
        if self.db_type == "sqlite":
            placeholders = ",".join(["?"] * len(clean_imdb_ids))
        
        query = f"""
        SELECT 
            mm."ImdbId" as imdb_id,
            m."Id" as movie_id,
            MIN(h."Date") as earliest_import
        FROM "Movies" m
        JOIN "MovieMetadata" mm ON m."MovieMetadataId" = mm."Id"
        LEFT JOIN "History" h ON m."Id" = h."MovieId" AND h."EventType" = 3
        WHERE mm."ImdbId" IN ({placeholders})
        GROUP BY mm."ImdbId", m."Id"
        """
        
        results = {}
        
        try:
            with self._get_connection() as conn:
                if self.db_type == "postgresql":
                    cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
                else:
                    cursor = conn.cursor()
                    
                cursor.execute(query, clean_imdb_ids)
                rows = cursor.fetchall()
                
                for row in rows:
                    if self.db_type == "postgresql":
                        imdb_id, movie_id, earliest_import = row['imdb_id'], row['movie_id'], row['earliest_import']
                    else:
                        imdb_id, movie_id, earliest_import = row[0], row[1], row[2]
                    
                    if earliest_import:
                        if isinstance(earliest_import, str):
                            dt = datetime.fromisoformat(earliest_import.replace("Z", "+00:00"))
                        else:
                            dt = earliest_import.replace(tzinfo=timezone.utc)
                        
                        date_iso = dt.astimezone(timezone.utc).isoformat(timespec="seconds")
                        results[imdb_id] = (date_iso, "radarr:db.bulk.import")
                    else:
                        results[imdb_id] = (None, "radarr:db.bulk.no_import")
                
        except Exception as e:
            _log("ERROR", f"Bulk query error: {e}")
            # Return empty results for failed queries
            for imdb_id in clean_imdb_ids:
                if imdb_id not in results:
                    results[imdb_id] = (None, "radarr:db.bulk.error")
        
        return results
    
    def get_database_stats(self) -> Dict[str, Any]:
        """Get basic statistics about the Radarr database"""
        stats = {}
        
        queries = {
            "total_movies": 'SELECT COUNT(*) FROM "Movies"',
            "total_movie_files": 'SELECT COUNT(*) FROM "MovieFiles"',
            "total_history_events": 'SELECT COUNT(*) FROM "History"',
            "import_events": 'SELECT COUNT(*) FROM "History" WHERE "EventType" = 3',
            "grab_events": 'SELECT COUNT(*) FROM "History" WHERE "EventType" = 1'
        }
        
        try:
            with self._get_connection() as conn:
                cursor = conn.cursor()
                
                for stat_name, query in queries.items():
                    cursor.execute(query)
                    result = cursor.fetchone()
                    stats[stat_name] = result[0] if result else 0
                    
        except Exception as e:
            _log("ERROR", f"Stats query error: {e}")
            stats["error"] = str(e)
            
        return stats
    
    def health_check(self) -> Dict[str, Any]:
        """
        Comprehensive health check for the Radarr database connection
        
        Returns:
            Dictionary with health status, connection info, and basic functionality tests
        """
        health = {
            "status": "healthy",
            "database_type": self.db_type,
            "connection": "ok",
            "readable": False,
            "writable": False,
            "tables_exist": False,
            "sample_data": False,
            "issues": [],
            "tested_at": datetime.now(timezone.utc).isoformat(timespec="seconds")
        }
        
        try:
            # Test 1: Basic connection
            with self._get_connection() as conn:
                cursor = conn.cursor()
                
                # Test 2: Check if we can read (basic query)
                try:
                    cursor.execute('SELECT 1')
                    result = cursor.fetchone()
                    if result and result[0] == 1:
                        health["readable"] = True
                        health["connection"] = "readable"
                    else:
                        health["issues"].append("Basic SELECT query failed")
                except Exception as e:
                    health["issues"].append(f"Read test failed: {e}")
                    health["status"] = "degraded"
                
                # Test 3: Check required tables exist
                required_tables = ["Movies", "MovieMetadata", "History", "MovieFiles"]
                existing_tables = []
                
                try:
                    if self.db_type == "postgresql":
                        cursor.execute("""
                            SELECT table_name 
                            FROM information_schema.tables 
                            WHERE table_schema = 'public' 
                            AND table_name IN ('Movies', 'MovieMetadata', 'History', 'MovieFiles')
                        """)
                    else:  # SQLite
                        cursor.execute("""
                            SELECT name 
                            FROM sqlite_master 
                            WHERE type='table' 
                            AND name IN ('Movies', 'MovieMetadata', 'History', 'MovieFiles')
                        """)
                    
                    rows = cursor.fetchall()
                    existing_tables = [row[0] for row in rows]
                    
                    if len(existing_tables) == len(required_tables):
                        health["tables_exist"] = True
                    else:
                        missing = set(required_tables) - set(existing_tables)
                        health["issues"].append(f"Missing tables: {list(missing)}")
                        health["status"] = "degraded"
                        
                    health["existing_tables"] = existing_tables
                    
                except Exception as e:
                    health["issues"].append(f"Table check failed: {e}")
                    health["status"] = "degraded"
                
                # Test 4: Check for sample data
                if health["tables_exist"]:
                    try:
                        cursor.execute('SELECT COUNT(*) FROM "Movies"')
                        movie_count = cursor.fetchone()[0]
                        
                        cursor.execute('SELECT COUNT(*) FROM "History"')
                        history_count = cursor.fetchone()[0]
                        
                        if movie_count > 0 and history_count > 0:
                            health["sample_data"] = True
                            health["movie_count"] = movie_count
                            health["history_count"] = history_count
                        else:
                            health["issues"].append(f"Low data counts - Movies: {movie_count}, History: {history_count}")
                            
                    except Exception as e:
                        health["issues"].append(f"Sample data check failed: {e}")
                
                # Test 5: Test a real query (movie with IMDb lookup)
                if health["sample_data"]:
                    try:
                        cursor.execute("""
                            SELECT COUNT(*) 
                            FROM "Movies" m
                            JOIN "MovieMetadata" mm ON m."MovieMetadataId" = mm."Id"
                            WHERE mm."ImdbId" IS NOT NULL
                        """)
                        imdb_movies = cursor.fetchone()[0]
                        health["movies_with_imdb"] = imdb_movies
                        
                        if imdb_movies > 0:
                            health["functional"] = True
                        else:
                            health["issues"].append("No movies with IMDb IDs found")
                            
                    except Exception as e:
                        health["issues"].append(f"Functional test failed: {e}")
                        health["status"] = "degraded"
        
        except Exception as e:
            health["status"] = "error"
            health["connection"] = "failed"
            health["issues"].append(f"Connection failed: {e}")
            _log("ERROR", f"Database health check failed: {e}")
        
        # Overall status determination
        if health["issues"]:
            if health["status"] == "healthy":
                health["status"] = "degraded"
        
        # Add connection details (safe info only)
        health["connection_info"] = {
            "type": self.db_type,
            "host": self.db_host if self.db_type == "postgresql" else None,
            "port": self.db_port if self.db_type == "postgresql" else None,
            "database": self.db_name if self.db_type == "postgresql" else None,
            "path": self.db_path if self.db_type == "sqlite" else None
        }
        
        return health


if __name__ == "__main__":
    # Test the database client
    print("Testing RadarrDbClient...")
    
    # Test with environment variables
    client = RadarrDbClient.from_env()
    if client:
        print("✅ Connected to Radarr database")
        
        # Test stats
        stats = client.get_database_stats()
        print(f"Database stats: {stats}")
        
        # Test movie lookup
        test_movie = client.get_movie_by_imdb("tt1596343")
        if test_movie:
            print(f"Found test movie: {test_movie}")
            
            # Test import date
            movie_id = test_movie['id']
            date_iso, source = client.get_movie_import_date_optimized(movie_id)
            print(f"Import date: {date_iso} (source: {source})")
        else:
            print("Test movie not found")
    else:
        print("❌ Could not connect to database - check environment variables")