"""
Configuration loading for waterfall enrichment.

Supports YAML and JSON configuration files with validation.
"""

import json
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional, Union

try:
    import yaml

    HAS_YAML = True
except ImportError:
    HAS_YAML = False

from .models import ProviderPlan


class ConfigError(Exception):
    """Error loading or validating waterfall configuration."""

    pass


@dataclass
class CacheConfig:
    """Cache configuration."""

    enabled: bool = True
    priority: int = 0
    ttl_seconds: int = 86400  # 24 hours


@dataclass
class RetryConfig:
    """Retry configuration for provider calls."""

    max_attempts: int = 3
    base_delay: float = 1.0
    max_delay: float = 30.0


@dataclass
class RateLimitConfig:
    """Rate limiting configuration."""

    requests_per_minute: int = 60


@dataclass
class OutputConfig:
    """Output configuration."""

    provenance_mode: str = "sidecar"  # "sidecar" or "none"
    metadata_columns: Dict[str, str] = field(
        default_factory=lambda: {
            "enriched_by": "_enriched_by",
            "fields_filled": "_fields_filled",
            "provider_errors": "_provider_errors",
        }
    )


@dataclass
class WaterfallConfig:
    """Complete waterfall configuration."""

    schema_version: str
    name: str
    cache: CacheConfig
    providers: List[ProviderPlan]
    stop_condition_expression: str
    retry_config: RetryConfig
    rate_limit: RateLimitConfig
    output: OutputConfig

    # Source file path (for relative path resolution)
    source_path: Optional[Path] = None

    def get_providers_by_priority(self) -> List[ProviderPlan]:
        """Return providers sorted by priority (lower = earlier)."""
        return sorted(
            [p for p in self.providers if p.enabled],
            key=lambda p: p.priority,
        )

    def get_provider(self, name: str) -> Optional[ProviderPlan]:
        """Get provider by name."""
        for p in self.providers:
            if p.name == name:
                return p
        return None


def load_config(config_path: Union[str, Path]) -> WaterfallConfig:
    """Load waterfall configuration from YAML or JSON file.

    Args:
        config_path: Path to configuration file (.yaml, .yml, or .json)

    Returns:
        Validated WaterfallConfig

    Raises:
        ConfigError: If file cannot be loaded or validation fails
    """
    path = Path(config_path)

    if not path.exists():
        raise ConfigError(f"Configuration file not found: {path}")

    ext = path.suffix.lower()

    try:
        if ext in (".yaml", ".yml"):
            if not HAS_YAML:
                raise ConfigError("YAML support requires PyYAML: pip install pyyaml")
            with path.open("r", encoding="utf-8") as f:
                raw = yaml.safe_load(f)
        elif ext == ".json":
            with path.open("r", encoding="utf-8") as f:
                raw = json.load(f)
        else:
            raise ConfigError(f"Unsupported config file extension: {ext} (expected .yaml, .yml, or .json)")
    except (yaml.YAMLError if HAS_YAML else Exception) as e:
        raise ConfigError(f"Failed to parse YAML: {e}")
    except json.JSONDecodeError as e:
        raise ConfigError(f"Failed to parse JSON: {e}")

    return _parse_config(raw, path)


def _parse_config(raw: Dict[str, Any], source_path: Path) -> WaterfallConfig:
    """Parse and validate raw configuration dictionary."""
    if not isinstance(raw, dict):
        raise ConfigError("Configuration must be a dictionary")

    # Schema version
    schema_version = raw.get("schema_version", "1")
    if schema_version not in ("1", 1):
        raise ConfigError(f"Unsupported schema_version: {schema_version}")

    # Name
    name = raw.get("name", "unnamed")

    # Cache config
    cache_raw = raw.get("cache", {})
    cache = CacheConfig(
        enabled=cache_raw.get("enabled", True),
        priority=cache_raw.get("priority", 0),
        ttl_seconds=cache_raw.get("ttl_seconds", 86400),
    )

    # Providers
    providers_raw = raw.get("providers", [])
    if not providers_raw:
        raise ConfigError("At least one provider must be configured")

    providers = []
    for i, p in enumerate(providers_raw):
        try:
            provider = _parse_provider(p)
            providers.append(provider)
        except Exception as e:
            raise ConfigError(f"Invalid provider at index {i}: {e}")

    # Stop condition
    stop_raw = raw.get("stop_condition", {})
    stop_expression = stop_raw.get("expression", "")
    if not stop_expression:
        # Default: any field filled
        stop_expression = " OR ".join(
            field for p in providers for field in p.fields[:1]  # First field from each provider
        )

    # Defaults
    defaults_raw = raw.get("defaults", {})
    retry_raw = defaults_raw.get("retry_config", {})
    retry_config = RetryConfig(
        max_attempts=retry_raw.get("max_attempts", 3),
        base_delay=retry_raw.get("base_delay", 1.0),
        max_delay=retry_raw.get("max_delay", 30.0),
    )

    rate_raw = defaults_raw.get("rate_limit", {})
    rate_limit = RateLimitConfig(
        requests_per_minute=rate_raw.get("requests_per_minute", 60),
    )

    # Output config
    output_raw = raw.get("output", {})
    metadata_raw = output_raw.get("metadata_columns", {})
    output = OutputConfig(
        provenance_mode=output_raw.get("provenance_mode", "sidecar"),
        metadata_columns={
            "enriched_by": metadata_raw.get("enriched_by", "_enriched_by"),
            "fields_filled": metadata_raw.get("fields_filled", "_fields_filled"),
            "provider_errors": metadata_raw.get("provider_errors", "_provider_errors"),
        },
    )

    return WaterfallConfig(
        schema_version=str(schema_version),
        name=name,
        cache=cache,
        providers=providers,
        stop_condition_expression=stop_expression,
        retry_config=retry_config,
        rate_limit=rate_limit,
        output=output,
        source_path=source_path,
    )


def _parse_provider(raw: Dict[str, Any]) -> ProviderPlan:
    """Parse a single provider configuration."""
    if not isinstance(raw, dict):
        raise ConfigError("Provider must be a dictionary")

    name = raw.get("name")
    if not name:
        raise ConfigError("Provider must have a 'name' field")

    priority = raw.get("priority", 99)
    fields = raw.get("fields", [])
    if not fields:
        raise ConfigError(f"Provider '{name}' must have at least one field")

    # Limits
    limits_raw = raw.get("limits", {})
    max_rows = limits_raw.get("max_rows")
    max_calls = limits_raw.get("max_calls")
    timeout_seconds = limits_raw.get("timeout_seconds", 60)
    budget_credits = limits_raw.get("budget_credits")

    # Field mapping
    field_mapping = raw.get("field_mapping", {})

    return ProviderPlan(
        name=name,
        priority=priority,
        fields=list(fields),
        enabled=raw.get("enabled", True),
        max_rows=max_rows,
        max_calls=max_calls,
        timeout_seconds=timeout_seconds,
        budget_credits=budget_credits,
        field_mapping=field_mapping,
    )


def merge_cli_overrides(
    config: WaterfallConfig,
    source_override: Optional[List[str]] = None,
    missing_override: Optional[List[str]] = None,
) -> WaterfallConfig:
    """Merge CLI overrides into configuration.

    Args:
        config: Base configuration
        source_override: CLI --source list (overrides provider order)
        missing_override: CLI --missing list (overrides requested fields)

    Returns:
        Updated configuration
    """
    if source_override:
        # Reorder providers based on CLI --source
        provider_map = {p.name: p for p in config.providers}
        new_priority = 1
        for name in source_override:
            if name in provider_map:
                provider_map[name].priority = new_priority
                new_priority += 1
            # Providers not in --source get pushed to end
        for name, provider in provider_map.items():
            if name not in source_override:
                provider.priority = new_priority
                provider.enabled = False  # Disable providers not in CLI list
                new_priority += 1

    # Note: missing_override would update stop_condition, but that requires
    # rebuilding the expression. For MVP, we leave stop_condition unchanged
    # and let the runner request all fields from the first provider that has them.

    return config


def create_default_config(providers: Optional[List[str]] = None) -> WaterfallConfig:
    """Create a default waterfall configuration.

    Used when no config file is provided but --waterfall is enabled.

    Args:
        providers: List of provider names in priority order

    Returns:
        Default WaterfallConfig
    """
    if providers is None:
        providers = ["foundrygraph", "apollo", "pdl", "apify", "zoominfo"]

    provider_defaults = {
        "foundrygraph": {
            "fields": ["domain", "canonical_name", "industries", "employee_count", "headquarters"],
            "timeout_seconds": 30,
        },
        "apollo": {
            "fields": ["domain", "industry", "employee_count", "linkedin_url", "founded_year", "email", "phone"],
            "timeout_seconds": 60,
            "max_calls": 1000,
        },
        "pdl": {
            "fields": [
                "domain",
                "name",
                "industry",
                "employee_count",
                "linkedin_url",
                "founded_year",
                "revenue",
            ],
            "timeout_seconds": 60,
        },
        "clearbit": {
            "fields": [
                "domain",
                "name",
                "industry",
                "employee_count",
                "employee_range",
                "linkedin_url",
                "twitter_url",
                "founded_year",
                "revenue",
                "website",
                "country",
                "region",
                "locality",
            ],
            "timeout_seconds": 60,
        },
        "apify": {
            "fields": ["website", "linkedin_url", "twitter_url"],
            "timeout_seconds": 120,
            "max_rows": 500,
        },
        "zoominfo": {
            "fields": ["revenue", "employee_count", "headquarters", "industry"],
            "timeout_seconds": 90,
            "max_rows": 200,
        },
    }

    provider_plans = []
    for i, name in enumerate(providers, start=1):
        defaults = provider_defaults.get(name, {"fields": ["domain"], "timeout_seconds": 60})
        provider_plans.append(
            ProviderPlan(
                name=name,
                priority=i,
                fields=defaults["fields"],
                timeout_seconds=defaults.get("timeout_seconds", 60),
                max_rows=defaults.get("max_rows"),
                max_calls=defaults.get("max_calls"),
            )
        )

    return WaterfallConfig(
        schema_version="1",
        name="default",
        cache=CacheConfig(),
        providers=provider_plans,
        stop_condition_expression="domain",  # Minimal default
        retry_config=RetryConfig(),
        rate_limit=RateLimitConfig(),
        output=OutputConfig(),
    )
