"""
Runtime Configuration Validator for NFOGuard
Provides validation of configuration at runtime with health checks
"""
import asyncio
import time
from pathlib import Path
from typing import Dict, List, Any, Optional, Tuple
from dataclasses import dataclass, field
from contextlib import asynccontextmanager
import aiohttp
import sqlite3
import psycopg2

from config.validator import ValidationIssue, ValidationResult, ValidationSeverity
from utils.exceptions import ConfigurationError, NetworkRetryableError, DatabaseError


@dataclass
class HealthCheckResult:
    """Result of a runtime health check"""
    component: str
    is_healthy: bool
    response_time_ms: Optional[float] = None
    message: str = ""
    details: Dict[str, Any] = field(default_factory=dict)
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "component": self.component,
            "is_healthy": self.is_healthy,
            "response_time_ms": self.response_time_ms,
            "message": self.message,
            "details": self.details,
            "timestamp": time.time()
        }


class RuntimeValidator:
    """Runtime configuration and system health validator"""
    
    def __init__(self, config):
        self.config = config
        self._health_cache = {}
        self._cache_ttl = 60  # Cache health results for 60 seconds
    
    async def validate_runtime_config(self) -> ValidationResult:
        """Perform runtime validation of configuration"""
        result = ValidationResult(is_valid=True)
        
        # Validate filesystem access
        await self._validate_filesystem_access(result)
        
        # Validate database connectivity
        await self._validate_database_connectivity(result)
        
        # Validate external API connectivity
        await self._validate_api_connectivity(result)
        
        # Validate permissions
        await self._validate_permissions(result)
        
        # Validate resource availability
        await self._validate_resources(result)
        
        return result
    
    async def _validate_filesystem_access(self, result: ValidationResult) -> None:
        """Validate filesystem access for media paths"""
        all_paths = []
        
        # Collect all media paths
        all_paths.extend(self.config.tv_paths)
        all_paths.extend(self.config.movie_paths)
        
        for path in all_paths:
            try:
                # Check if path exists and is accessible
                if not path.exists():
                    result.add_issue(ValidationIssue(
                        setting="media_paths",
                        severity=ValidationSeverity.ERROR,
                        message=f"Media path does not exist: {path}",
                        current_value=str(path)
                    ))
                    continue
                
                # Check if path is readable
                test_file = None
                try:
                    # Try to list directory contents
                    list(path.iterdir())
                except PermissionError:
                    result.add_issue(ValidationIssue(
                        setting="media_paths",
                        severity=ValidationSeverity.ERROR,
                        message=f"No read permission for media path: {path}",
                        current_value=str(path)
                    ))
                    continue
                except OSError as e:
                    result.add_issue(ValidationIssue(
                        setting="media_paths",
                        severity=ValidationSeverity.WARNING,
                        message=f"Error accessing media path: {e}",
                        current_value=str(path)
                    ))
                    continue
                
                # Check write permissions (needed for NFO files)
                try:
                    test_file = path / ".nfoguard_write_test"
                    test_file.write_text("test")
                    test_file.unlink()
                except PermissionError:
                    result.add_issue(ValidationIssue(
                        setting="media_paths",
                        severity=ValidationSeverity.ERROR,
                        message=f"No write permission for media path: {path}",
                        current_value=str(path),
                        details={"required_for": "NFO file creation"}
                    ))
                except OSError as e:
                    result.add_issue(ValidationIssue(
                        setting="media_paths",
                        severity=ValidationSeverity.WARNING,
                        message=f"Write test failed for media path: {e}",
                        current_value=str(path)
                    ))
                finally:
                    # Cleanup test file if it exists
                    if test_file and test_file.exists():
                        try:
                            test_file.unlink()
                        except:
                            pass
                            
            except Exception as e:
                result.add_issue(ValidationIssue(
                    setting="media_paths",
                    severity=ValidationSeverity.ERROR,
                    message=f"Unexpected error accessing path: {e}",
                    current_value=str(path)
                ))
        
        # Validate database directory
        db_path = Path(self.config.db_path)
        db_dir = db_path.parent
        
        if not db_dir.exists():
            try:
                db_dir.mkdir(parents=True, exist_ok=True)
            except PermissionError:
                result.add_issue(ValidationIssue(
                    setting="DB_PATH",
                    severity=ValidationSeverity.ERROR,
                    message=f"Cannot create database directory: {db_dir}",
                    current_value=str(db_path)
                ))
            except OSError as e:
                result.add_issue(ValidationIssue(
                    setting="DB_PATH",
                    severity=ValidationSeverity.ERROR,
                    message=f"Error creating database directory: {e}",
                    current_value=str(db_path)
                ))
        
        # Test database file access
        if not db_path.exists():
            try:
                # Try to create the database
                with sqlite3.connect(str(db_path)) as conn:
                    conn.execute("SELECT 1")
            except PermissionError:
                result.add_issue(ValidationIssue(
                    setting="DB_PATH",
                    severity=ValidationSeverity.ERROR,
                    message=f"Cannot create database file: {db_path}",
                    current_value=str(db_path)
                ))
            except sqlite3.Error as e:
                result.add_issue(ValidationIssue(
                    setting="DB_PATH",
                    severity=ValidationSeverity.ERROR,
                    message=f"Database error: {e}",
                    current_value=str(db_path)
                ))
    
    async def _validate_database_connectivity(self, result: ValidationResult) -> None:
        """Validate database connectivity"""
        # Test Radarr database if configured
        if hasattr(self.config, 'db_type') or 'RADARR_DB_TYPE' in os.environ:
            import os
            db_type = getattr(self.config, 'db_type', os.environ.get('RADARR_DB_TYPE', '')).lower()
            
            if db_type == 'postgresql':
                await self._test_postgresql_connection(result)
            elif db_type == 'sqlite':
                await self._test_sqlite_connection(result)
        
        # Test local SQLite database
        await self._test_local_database(result)
    
    async def _test_postgresql_connection(self, result: ValidationResult) -> None:
        """Test PostgreSQL connection"""
        import os
        
        try:
            host = os.environ.get('RADARR_DB_HOST')
            port = int(os.environ.get('RADARR_DB_PORT', 5432))
            database = os.environ.get('RADARR_DB_NAME')
            user = os.environ.get('RADARR_DB_USER')
            password = os.environ.get('RADARR_DB_PASSWORD', '')
            
            start_time = time.time()
            
            # Use asyncio to run blocking DB call
            def test_connection():
                conn = psycopg2.connect(
                    host=host, port=port, database=database,
                    user=user, password=password,
                    connect_timeout=10
                )
                with conn:
                    with conn.cursor() as cur:
                        cur.execute("SELECT 1")
                return conn
            
            conn = await asyncio.get_event_loop().run_in_executor(None, test_connection)
            conn.close()
            
            response_time = (time.time() - start_time) * 1000
            
            if response_time > 5000:  # 5 seconds
                result.add_issue(ValidationIssue(
                    setting="RADARR_DB",
                    severity=ValidationSeverity.WARNING,
                    message=f"Slow database connection ({response_time:.0f}ms)",
                    details={"response_time_ms": response_time}
                ))
                
        except psycopg2.OperationalError as e:
            result.add_issue(ValidationIssue(
                setting="RADARR_DB",
                severity=ValidationSeverity.ERROR,
                message=f"Cannot connect to PostgreSQL database: {e}",
                details={"error_type": "connection_failed"}
            ))
        except Exception as e:
            result.add_issue(ValidationIssue(
                setting="RADARR_DB",
                severity=ValidationSeverity.ERROR,
                message=f"Database connection error: {e}",
                details={"error_type": "unexpected_error"}
            ))
    
    async def _test_local_database(self, result: ValidationResult) -> None:
        """Test local SQLite database"""
        try:
            db_path = Path(self.config.db_path)
            
            start_time = time.time()
            
            def test_db():
                with sqlite3.connect(str(db_path), timeout=10) as conn:
                    conn.execute("SELECT 1")
                    return True
            
            await asyncio.get_event_loop().run_in_executor(None, test_db)
            
            response_time = (time.time() - start_time) * 1000
            
            if response_time > 1000:  # 1 second is slow for SQLite
                result.add_issue(ValidationIssue(
                    setting="DB_PATH",
                    severity=ValidationSeverity.WARNING,
                    message=f"Slow local database access ({response_time:.0f}ms)",
                    current_value=str(db_path),
                    details={"response_time_ms": response_time}
                ))
                
        except sqlite3.OperationalError as e:
            result.add_issue(ValidationIssue(
                setting="DB_PATH",
                severity=ValidationSeverity.ERROR,
                message=f"Local database error: {e}",
                current_value=str(self.config.db_path)
            ))
    
    async def _validate_api_connectivity(self, result: ValidationResult) -> None:
        """Validate external API connectivity"""
        apis = []
        
        if hasattr(self.config, 'radarr_url') and self.config.radarr_url:
            apis.append(("Radarr", self.config.radarr_url))
        
        if hasattr(self.config, 'sonarr_url') and self.config.sonarr_url:
            apis.append(("Sonarr", self.config.sonarr_url))
        
        for api_name, base_url in apis:
            await self._test_api_connectivity(result, api_name, base_url)
    
    async def _test_api_connectivity(self, result: ValidationResult, api_name: str, base_url: str) -> None:
        """Test connectivity to a specific API"""
        try:
            timeout = aiohttp.ClientTimeout(total=10)
            
            async with aiohttp.ClientSession(timeout=timeout) as session:
                start_time = time.time()
                
                # Test basic connectivity
                test_url = f"{base_url.rstrip('/')}/api/v1/health"
                
                async with session.get(test_url) as response:
                    response_time = (time.time() - start_time) * 1000
                    
                    if response.status == 200:
                        if response_time > 5000:  # 5 seconds
                            result.add_issue(ValidationIssue(
                                setting=f"{api_name.upper()}_URL",
                                severity=ValidationSeverity.WARNING,
                                message=f"Slow {api_name} API response ({response_time:.0f}ms)",
                                current_value=base_url,
                                details={"response_time_ms": response_time}
                            ))
                    else:
                        result.add_issue(ValidationIssue(
                            setting=f"{api_name.upper()}_URL",
                            severity=ValidationSeverity.WARNING,
                            message=f"{api_name} API returned HTTP {response.status}",
                            current_value=base_url,
                            details={"status_code": response.status}
                        ))
                        
        except asyncio.TimeoutError:
            result.add_issue(ValidationIssue(
                setting=f"{api_name.upper()}_URL",
                severity=ValidationSeverity.WARNING,
                message=f"{api_name} API connection timeout",
                current_value=base_url,
                details={"error_type": "timeout"}
            ))
        except Exception as e:
            result.add_issue(ValidationIssue(
                setting=f"{api_name.upper()}_URL",
                severity=ValidationSeverity.WARNING,
                message=f"{api_name} API connection error: {e}",
                current_value=base_url,
                details={"error_type": "connection_error"}
            ))
    
    async def _validate_permissions(self, result: ValidationResult) -> None:
        """Validate file system permissions"""
        # This is partially covered in filesystem validation
        # Additional permission checks can be added here
        pass
    
    async def _validate_resources(self, result: ValidationResult) -> None:
        """Validate system resources"""
        import shutil
        
        try:
            # Check disk space for database directory
            db_path = Path(self.config.db_path)
            db_dir = db_path.parent if not db_path.is_dir() else db_path
            
            if db_dir.exists():
                free_space = shutil.disk_usage(str(db_dir)).free
                free_space_mb = free_space / (1024 * 1024)
                
                if free_space_mb < 100:  # Less than 100MB
                    result.add_issue(ValidationIssue(
                        setting="DB_PATH",
                        severity=ValidationSeverity.ERROR,
                        message=f"Low disk space in database directory ({free_space_mb:.1f}MB free)",
                        current_value=str(db_path),
                        details={"free_space_mb": free_space_mb}
                    ))
                elif free_space_mb < 1000:  # Less than 1GB
                    result.add_issue(ValidationIssue(
                        setting="DB_PATH",
                        severity=ValidationSeverity.WARNING,
                        message=f"Low disk space in database directory ({free_space_mb:.1f}MB free)",
                        current_value=str(db_path),
                        details={"free_space_mb": free_space_mb}
                    ))
                    
        except Exception as e:
            result.add_issue(ValidationIssue(
                setting="system_resources",
                severity=ValidationSeverity.WARNING,
                message=f"Could not check disk space: {e}",
                details={"error_type": "resource_check_failed"}
            ))
    
    async def get_system_health(self) -> Dict[str, HealthCheckResult]:
        """Get comprehensive system health status"""
        health_checks = {}
        
        # Database health
        health_checks["database"] = await self._check_database_health()
        
        # Filesystem health
        health_checks["filesystem"] = await self._check_filesystem_health()
        
        # API health
        health_checks["external_apis"] = await self._check_api_health()
        
        return health_checks
    
    async def _check_database_health(self) -> HealthCheckResult:
        """Check database health"""
        try:
            start_time = time.time()
            
            def test_db():
                with sqlite3.connect(str(self.config.db_path), timeout=5) as conn:
                    conn.execute("SELECT COUNT(*) FROM sqlite_master")
                    return True
            
            await asyncio.get_event_loop().run_in_executor(None, test_db)
            
            response_time = (time.time() - start_time) * 1000
            
            return HealthCheckResult(
                component="database",
                is_healthy=True,
                response_time_ms=response_time,
                message="Database accessible",
                details={"db_path": str(self.config.db_path)}
            )
            
        except Exception as e:
            return HealthCheckResult(
                component="database",
                is_healthy=False,
                message=f"Database error: {e}",
                details={"db_path": str(self.config.db_path), "error": str(e)}
            )
    
    async def _check_filesystem_health(self) -> HealthCheckResult:
        """Check filesystem health"""
        try:
            accessible_paths = 0
            total_paths = len(self.config.tv_paths) + len(self.config.movie_paths)
            
            for path in list(self.config.tv_paths) + list(self.config.movie_paths):
                if path.exists() and path.is_dir():
                    try:
                        # Quick access test
                        next(path.iterdir(), None)
                        accessible_paths += 1
                    except:
                        pass
            
            is_healthy = accessible_paths == total_paths
            health_percentage = (accessible_paths / total_paths * 100) if total_paths > 0 else 0
            
            return HealthCheckResult(
                component="filesystem",
                is_healthy=is_healthy,
                message=f"{accessible_paths}/{total_paths} media paths accessible ({health_percentage:.1f}%)",
                details={
                    "accessible_paths": accessible_paths,
                    "total_paths": total_paths,
                    "health_percentage": health_percentage
                }
            )
            
        except Exception as e:
            return HealthCheckResult(
                component="filesystem",
                is_healthy=False,
                message=f"Filesystem check error: {e}",
                details={"error": str(e)}
            )
    
    async def _check_api_health(self) -> HealthCheckResult:
        """Check external API health"""
        apis_tested = 0
        apis_healthy = 0
        api_details = {}
        
        try:
            # Test configured APIs
            if hasattr(self.config, 'radarr_url') and self.config.radarr_url:
                apis_tested += 1
                healthy = await self._test_single_api("Radarr", self.config.radarr_url)
                if healthy:
                    apis_healthy += 1
                api_details["radarr"] = {"healthy": healthy}
            
            if hasattr(self.config, 'sonarr_url') and self.config.sonarr_url:
                apis_tested += 1
                healthy = await self._test_single_api("Sonarr", self.config.sonarr_url)
                if healthy:
                    apis_healthy += 1
                api_details["sonarr"] = {"healthy": healthy}
            
            if apis_tested == 0:
                return HealthCheckResult(
                    component="external_apis",
                    is_healthy=True,
                    message="No external APIs configured",
                    details={"apis_configured": 0}
                )
            
            is_healthy = apis_healthy == apis_tested
            health_percentage = (apis_healthy / apis_tested * 100) if apis_tested > 0 else 0
            
            return HealthCheckResult(
                component="external_apis",
                is_healthy=is_healthy,
                message=f"{apis_healthy}/{apis_tested} APIs healthy ({health_percentage:.1f}%)",
                details={
                    "healthy_apis": apis_healthy,
                    "total_apis": apis_tested,
                    "health_percentage": health_percentage,
                    "api_status": api_details
                }
            )
            
        except Exception as e:
            return HealthCheckResult(
                component="external_apis",
                is_healthy=False,
                message=f"API health check error: {e}",
                details={"error": str(e)}
            )
    
    async def _test_single_api(self, api_name: str, base_url: str) -> bool:
        """Test a single API for health"""
        try:
            timeout = aiohttp.ClientTimeout(total=5)
            
            async with aiohttp.ClientSession(timeout=timeout) as session:
                test_url = f"{base_url.rstrip('/')}/api/v1/health"
                
                async with session.get(test_url) as response:
                    return response.status == 200
                    
        except:
            return False


# Import needed for database validation
import os