"""Configuration loading and validation utilities."""

from __future__ import annotations

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

import yaml

ENV_PATTERN = re.compile(r"\$\{([A-Z0-9_]+)\}")


@dataclass
class ConfigResolutionResult:
    """Result metadata from config environment interpolation."""

    config: dict[str, Any]
    missing_env_vars: set[str]


def load_and_resolve_config(config_path: str) -> dict[str, Any]:
    """Load YAML config, interpolate `${VAR}` values, and validate required settings."""
    path = Path(config_path)
    if not path.exists():
        raise FileNotFoundError(f"Config file not found: {config_path}")

    with path.open(encoding="utf-8") as handle:
        raw = yaml.safe_load(handle) or {}

    if not isinstance(raw, dict):
        raise RuntimeError("Root config must be a YAML mapping")

    resolution = resolve_env_vars(raw)
    validate_runtime_requirements(resolution.config, resolution.missing_env_vars)
    return resolution.config


def resolve_env_vars(value: Any) -> ConfigResolutionResult:
    """Recursively resolve `${VAR}` placeholders in config values."""
    missing: set[str] = set()
    resolved = _resolve_value(value, missing)
    return ConfigResolutionResult(config=resolved, missing_env_vars=missing)


def _resolve_value(value: Any, missing: set[str]) -> Any:
    """Resolve a config node and collect missing env vars."""
    if isinstance(value, dict):
        return {k: _resolve_value(v, missing) for k, v in value.items()}
    if isinstance(value, list):
        return [_resolve_value(item, missing) for item in value]
    if isinstance(value, str):
        return _resolve_string(value, missing)
    return value


def _resolve_string(value: str, missing: set[str]) -> str:
    """Resolve environment placeholders inside a string value."""

    def _replace(match: re.Match[str]) -> str:
        env_key = match.group(1)
        env_val = os.getenv(env_key)
        if env_val is None:
            missing.add(env_key)
            return ""
        return env_val

    return ENV_PATTERN.sub(_replace, value)


def validate_runtime_requirements(config: dict[str, Any], _missing_env_vars: set[str]) -> None:
    """Validate runtime-critical configuration and fail loudly on invalid setup."""
    errors: list[str] = []
    missing_by_path: list[str] = []

    def get(path: str, default: Any = None) -> Any:
        cur: Any = config
        for part in path.split("."):
            if not isinstance(cur, dict) or part not in cur:
                return default
            cur = cur[part]
        return cur

    def require_non_empty(path: str, label: str | None = None) -> None:
        value = get(path)
        if not isinstance(value, str) or not value.strip():
            missing_by_path.append(label or path)

    llm_provider = str(get("llm.provider", "openai")).strip().lower()
    if llm_provider in {"openai", "anthropic", "openai-compatible"}:
        require_non_empty("llm.api_key", "llm.api_key")
    if llm_provider not in {"openai", "anthropic", "openai-compatible", "ollama"}:
        errors.append(f"Unsupported llm.provider: {llm_provider}")

    email_provider = str(get("email.provider", "smtp")).strip().lower()
    if email_provider == "smtp":
        require_non_empty("email.smtp.host", "email.smtp.host")
    elif email_provider not in {"sendgrid", "mailgun", "smtp"}:
        errors.append(f"Unsupported email.provider: {email_provider}")

    crm_provider = str(get("crm.provider", "none")).strip().lower()
    if crm_provider == "hubspot":
        require_non_empty("crm.hubspot.api_key", "crm.hubspot.api_key")
    elif crm_provider == "salesforce":
        require_non_empty("crm.salesforce.client_id", "crm.salesforce.client_id")
        require_non_empty("crm.salesforce.client_secret", "crm.salesforce.client_secret")
    elif crm_provider not in {"none", "hubspot", "salesforce", "pipedrive"}:
        errors.append(f"Unsupported crm.provider: {crm_provider}")

    klytics_enabled = bool(get("klytics.enabled", False))
    if klytics_enabled:
        require_non_empty("klytics.webhook_url", "klytics.webhook_url")
        threshold = get("klytics.lead_threshold", 60)
        try:
            threshold_int = int(threshold)
            if threshold_int < 0 or threshold_int > 100:
                errors.append("klytics.lead_threshold must be between 0 and 100")
        except (TypeError, ValueError):
            errors.append("klytics.lead_threshold must be an integer")

    if missing_by_path:
        errors.append(f"Missing required config values: {', '.join(sorted(set(missing_by_path)))}")

    if errors:
        raise RuntimeError("Configuration validation failed:\n- " + "\n- ".join(errors))
