from pathlib import Path
from pydantic import Field, TypeAdapter, ValidationError as PydanticValidationError, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Self
import logging
import msgspec
from core.exceptions import ConfigurationError
from infrastructure.file_utils import atomic_write_json, read_json

logger = logging.getLogger(__name__)

_VALID_LOG_LEVELS = frozenset({"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"})


class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        case_sensitive=False,
        extra="allow"
    )
    
    lidarr_url: str = Field(default="http://lidarr:8686")
    lidarr_api_key: str = Field(default="")
    lidarr_timeout: float = Field(
        default=30.0,
        ge=5.0,
        le=600.0,
        description="HTTP read/write timeout in seconds for Lidarr API calls.",
    )
    
    jellyfin_url: str = Field(default="http://jellyfin:8096")
    
    contact_email: str = Field(
        default="contact@musicseerr.com",
        description="Contact email for MusicBrainz API User-Agent. Override with your own if desired."
    )
    
    quality_profile_id: int = Field(default=1)
    metadata_profile_id: int = Field(default=1)
    root_folder_path: str = Field(default="/music")
    
    port: int = Field(default=8688)
    debug: bool = Field(default=False)
    log_level: str = Field(default="INFO")
    
    cache_ttl_default: int = Field(default=60)
    cache_ttl_artist: int = Field(default=3600)
    cache_ttl_album: int = Field(default=3600)
    cache_ttl_covers: int = Field(default=86400, description="Cover cache TTL in seconds (default: 24 hours)")
    cache_cleanup_interval: int = Field(default=300)
    
    root_app_dir: Path = Field(default=Path("/app"), description="Root application directory")
    cache_dir: Path = Field(default=Path("/app/cache"), description="Root directory for all cache files")
    library_db_path: Path = Field(default=Path("/app/cache/library.db"), description="SQLite library database path")
    cover_cache_max_size_mb: int = Field(default=500, description="Maximum cover cache size in MB")
    queue_db_path: Path = Field(default=Path("/app/cache/queue.db"), description="SQLite queue database path")
    shutdown_grace_period: float = Field(default=10.0, description="Seconds to wait for tasks on shutdown")
    
    http_timeout: float = Field(default=10.0)
    http_connect_timeout: float = Field(default=5.0)
    http_max_connections: int = Field(default=200)
    http_max_keepalive: int = Field(default=50)
    
    config_file_path: Path = Field(default=Path("/app/config/config.json"))
    audiodb_api_key: str = Field(default="123")
    audiodb_premium: bool = Field(default=False, description="Set to true if using a premium AudioDB API key")
    
    @field_validator("log_level")
    @classmethod
    def validate_log_level(cls, v: str) -> str:
        normalised = v.upper()
        if normalised not in _VALID_LOG_LEVELS:
            raise ValueError(
                f"Invalid log_level '{v}'. Must be one of: {', '.join(sorted(_VALID_LOG_LEVELS))}"
            )
        return normalised

    @field_validator("lidarr_url", "jellyfin_url")
    @classmethod
    def validate_url(cls, v: str) -> str:
        return v.rstrip("/")

    @model_validator(mode='after')
    def validate_config(self) -> Self:
        # Dynamically resolve paths relative to root_app_dir
        if self.cache_dir == Path("/app/cache"):
            self.cache_dir = self.root_app_dir / "cache"
        if self.library_db_path == Path("/app/cache/library.db"):
            self.library_db_path = self.cache_dir / "library.db"
        if self.queue_db_path == Path("/app/cache/queue.db"):
            self.queue_db_path = self.cache_dir / "queue.db"
        if self.config_file_path == Path("/app/config/config.json"):
            self.config_file_path = self.root_app_dir / "config" / "config.json"

        errors = []
        warnings = []

        for url_field in ['lidarr_url', 'jellyfin_url']:
            url = getattr(self, url_field, '')
            if url and not url.startswith(('http://', 'https://')):
                errors.append(f"{url_field} must start with http:// or https://")

        if self.http_max_connections < self.http_max_keepalive * 2:
            warnings.append(
                f"http_max_connections ({self.http_max_connections}) should be "
                f"at least 2x http_max_keepalive ({self.http_max_keepalive})"
            )

        if not self.lidarr_api_key:
            warnings.append("LIDARR_API_KEY is not set - Lidarr features will not work")

        for warning in warnings:
            logger.warning(warning)

        if errors:
            raise ConfigurationError(
                f"Critical configuration errors: {'; '.join(errors)}"
            )

        return self
    
    def get_user_agent(self) -> str:
        return f"Musicseerr/1.0 ({self.contact_email}; https://www.musicseerr.com)"

    def load_from_file(self) -> None:
        if not self.config_file_path.exists():
            self._create_default_config()
            return
        
        try:
            config_data = read_json(self.config_file_path, default={})
            if not isinstance(config_data, dict):
                raise ValueError("Config file JSON root must be an object")
            
            type_errors: list[str] = []
            model_fields = type(self).model_fields
            validated_values: dict[str, object] = {}
            for key, value in config_data.items():
                if key not in model_fields:
                    logger.warning("Unknown config key '%s', ignoring", key)
                    continue
                try:
                    field_info = model_fields[key]
                    adapter = TypeAdapter(field_info.annotation)
                    validated_values[key] = adapter.validate_python(value)
                except PydanticValidationError as e:
                    type_errors.append(
                        f"'{key}': {e.errors()[0].get('msg', str(e))}"
                    )
                except (TypeError, ValueError) as e:
                    type_errors.append(f"'{key}': {e}")

            if type_errors:
                raise ConfigurationError(
                    f"Config file type errors: {'; '.join(type_errors)}"
                )

            # Run field validators that TypeAdapter doesn't invoke
            try:
                for url_field in ('lidarr_url', 'jellyfin_url'):
                    if url_field in validated_values:
                        validated_values[url_field] = type(self).validate_url(
                            validated_values[url_field]
                        )
                if 'log_level' in validated_values:
                    validated_values['log_level'] = type(self).validate_log_level(
                        validated_values['log_level']
                    )
            except ValueError as e:
                raise ConfigurationError(f"Config file validation error: {e}")

            # Dry-run cross-field validation on merged candidate state
            self._validate_merged(validated_values)

            # All validation passed; apply atomically.
            for key, value in validated_values.items():
                setattr(self, key, value)

        except (ConfigurationError, ValueError):
            raise
        except msgspec.DecodeError as e:
            logger.error(f"Invalid JSON in config file: {e}")
            raise ValueError(f"Config file is not valid JSON: {e}")
        except Exception as e:
            logger.error(f"Failed to load config: {e}")
            raise

    def _validate_merged(self, overrides: dict[str, object]) -> None:
        """Validate cross-field constraints against candidate merged state without mutating self."""
        errors = []

        def _get(field: str) -> object:
            return overrides.get(field, getattr(self, field))

        for url_field in ('lidarr_url', 'jellyfin_url'):
            url = _get(url_field)
            if url and not str(url).startswith(('http://', 'https://')):
                errors.append(f"{url_field} must start with http:// or https://")

        if errors:
            raise ConfigurationError(
                f"Critical configuration errors: {'; '.join(errors)}"
            )
    
    def _create_default_config(self) -> None:
        self.config_file_path.parent.mkdir(parents=True, exist_ok=True)
        config_data = {
            "lidarr_url": self.lidarr_url,
            "lidarr_api_key": self.lidarr_api_key,
            "lidarr_timeout": self.lidarr_timeout,
            "jellyfin_url": self.jellyfin_url,
            "contact_email": self.contact_email,
            "quality_profile_id": self.quality_profile_id,
            "metadata_profile_id": self.metadata_profile_id,
            "root_folder_path": self.root_folder_path,
            "port": self.port,
            "audiodb_api_key": self.audiodb_api_key,
            "audiodb_premium": self.audiodb_premium,
            "user_preferences": {
                "primary_types": ["album", "ep", "single"],
                "secondary_types": ["studio"],
                "release_statuses": ["official"],
            },
        }
        atomic_write_json(self.config_file_path, config_data)
    
    def save_to_file(self) -> None:
        try:
            self.config_file_path.parent.mkdir(parents=True, exist_ok=True)
            
            config_data = {}
            if self.config_file_path.exists():
                loaded = read_json(self.config_file_path, default={})
                config_data = loaded if isinstance(loaded, dict) else {}
            
            config_data.update({
                "lidarr_url": self.lidarr_url,
                "lidarr_api_key": self.lidarr_api_key,
                "lidarr_timeout": self.lidarr_timeout,
                "jellyfin_url": self.jellyfin_url,
                "contact_email": self.contact_email,
                "quality_profile_id": self.quality_profile_id,
                "metadata_profile_id": self.metadata_profile_id,
                "root_folder_path": self.root_folder_path,
                "port": self.port,
                "audiodb_api_key": self.audiodb_api_key,
                "audiodb_premium": self.audiodb_premium,
            })
            
            atomic_write_json(self.config_file_path, config_data)
            
        except Exception as e:
            logger.error(f"Failed to save config: {e}")
            raise


_settings: Settings | None = None


def get_settings() -> Settings:
    global _settings
    if _settings is None:
        settings = Settings()
        settings.load_from_file()
        _settings = settings
    return _settings
