"""
Configuration file loader for storage backends.

Supports YAML configuration files with environment variable substitution.
"""

import os
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any

import yaml


@dataclass(frozen=True)
class LocalConfig:
    """Local storage backend configuration."""

    base_path: Path


@dataclass(frozen=True)
class AzureConfig:
    """Azure Blob Storage configuration."""

    connection_string: str
    container_name: str
    create_container: bool = False


@dataclass(frozen=True)
class S3Config:
    """AWS S3 configuration."""

    bucket_name: str
    region_name: str | None = None
    access_key_id: str | None = None
    secret_access_key: str | None = None
    endpoint_url: str | None = None
    create_bucket: bool = False


@dataclass(frozen=True)
class StorageFileConfig:
    """Extended storage configuration from file.

    Attributes:
        backend_type: Type of storage backend.
        local: Local backend configuration.
        azure: Azure Blob configuration.
        s3: S3 configuration.
        presigned_url_expiry: Default expiry for pre-signed URLs in seconds.
    """

    backend_type: str
    local: LocalConfig | None = None
    azure: AzureConfig | None = None
    s3: S3Config | None = None
    presigned_url_expiry: int = 3600


def substitute_env_vars(value: str) -> str:
    """Substitute environment variables in a string.

    Supports ${VAR_NAME} and ${VAR_NAME:-default} syntax.

    Args:
        value: String potentially containing env var references.

    Returns:
        String with env vars substituted.
    """
    pattern = r"\$\{([A-Z_][A-Z0-9_]*)(?::-([^}]*))?\}"

    def replace(match: re.Match[str]) -> str:
        var_name = match.group(1)
        default = match.group(2)
        return os.environ.get(var_name, default or "")

    return re.sub(pattern, replace, value)


def _substitute_in_dict(data: dict[str, Any]) -> dict[str, Any]:
    """Recursively substitute env vars in a dictionary.

    Args:
        data: Dictionary to process.

    Returns:
        New dictionary with substitutions applied.
    """
    result: dict[str, Any] = {}
    for key, value in data.items():
        if isinstance(value, str):
            result[key] = substitute_env_vars(value)
        elif isinstance(value, dict):
            result[key] = _substitute_in_dict(value)
        elif isinstance(value, list):
            result[key] = [
                substitute_env_vars(item) if isinstance(item, str) else item
                for item in value
            ]
        else:
            result[key] = value
    return result


def _parse_local_config(data: dict[str, Any]) -> LocalConfig:
    """Parse local configuration section.

    Args:
        data: Dictionary containing local config.

    Returns:
        LocalConfig instance.

    Raises:
        ValueError: If required fields are missing.
    """
    base_path = data.get("base_path")
    if not base_path:
        raise ValueError("local.base_path is required")
    return LocalConfig(base_path=Path(base_path))


def _parse_azure_config(data: dict[str, Any]) -> AzureConfig:
    """Parse Azure configuration section.

    Args:
        data: Dictionary containing Azure config.

    Returns:
        AzureConfig instance.

    Raises:
        ValueError: If required fields are missing.
    """
    connection_string = data.get("connection_string")
    container_name = data.get("container_name")

    if not connection_string:
        raise ValueError("azure.connection_string is required")
    if not container_name:
        raise ValueError("azure.container_name is required")

    return AzureConfig(
        connection_string=connection_string,
        container_name=container_name,
        create_container=data.get("create_container", False),
    )


def _parse_s3_config(data: dict[str, Any]) -> S3Config:
    """Parse S3 configuration section.

    Args:
        data: Dictionary containing S3 config.

    Returns:
        S3Config instance.

    Raises:
        ValueError: If required fields are missing.
    """
    bucket_name = data.get("bucket_name")

    if not bucket_name:
        raise ValueError("s3.bucket_name is required")

    return S3Config(
        bucket_name=bucket_name,
        region_name=data.get("region_name"),
        access_key_id=data.get("access_key_id"),
        secret_access_key=data.get("secret_access_key"),
        endpoint_url=data.get("endpoint_url"),
        create_bucket=data.get("create_bucket", False),
    )


def load_storage_config(config_path: Path | str) -> StorageFileConfig:
    """Load storage configuration from YAML file.

    Supports environment variable substitution using ${VAR_NAME} or
    ${VAR_NAME:-default} syntax.

    Args:
        config_path: Path to configuration file.

    Returns:
        Parsed StorageFileConfig.

    Raises:
        FileNotFoundError: If config file doesn't exist.
        ValueError: If config is invalid.
    """
    config_path = Path(config_path)

    if not config_path.exists():
        raise FileNotFoundError(f"Config file not found: {config_path}")

    try:
        raw_content = config_path.read_text(encoding="utf-8")
        data = yaml.safe_load(raw_content)
    except yaml.YAMLError as e:
        raise ValueError(f"Invalid YAML in config file: {e}") from e

    if not isinstance(data, dict):
        raise ValueError("Config file must contain a YAML dictionary")

    # Substitute environment variables
    data = _substitute_in_dict(data)

    # Extract backend type
    backend_type = data.get("backend")
    if not backend_type:
        raise ValueError("'backend' field is required in config file")

    # Parse presigned URL expiry
    presigned_url_expiry = data.get("presigned_url_expiry", 3600)

    # Parse backend-specific configurations
    local_config = None
    azure_config = None
    s3_config = None

    if "local" in data:
        local_config = _parse_local_config(data["local"])

    if "azure" in data:
        azure_config = _parse_azure_config(data["azure"])

    if "s3" in data:
        s3_config = _parse_s3_config(data["s3"])

    return StorageFileConfig(
        backend_type=backend_type,
        local=local_config,
        azure=azure_config,
        s3=s3_config,
        presigned_url_expiry=presigned_url_expiry,
    )
