from __future__ import annotations
from typing import Any, Dict, List, Optional
import logging
import os
import httpx

log = logging.getLogger(__name__)


class ConfigurationError(RuntimeError):
    """Raised when required configuration is missing."""
    pass


def _env_bool(name: str, default: bool = False) -> bool:
    v = os.getenv(name)
    if v is None:
        return default
    return str(v).lower() in ("1", "true", "yes", "y", "on")


class IntelligenceClient:
    """Thin client to call /api/v2/intelligence endpoints (same FastAPI app)."""

    def __init__(
        self, base_url: Optional[str] = None, headers: Optional[Dict[str, str]] = None
    ):
        # Prefer explicit arg; else environment; fail-fast if not configured
        # IMPORTANT: No localhost fallback - ensures production deployments are properly configured
        if not base_url:
            base_url = os.getenv("INTELLIGENCE_BASE_URL") or os.getenv("PUBLIC_BASE_URL")
        if not base_url:
            raise ConfigurationError(
                "INTELLIGENCE_BASE_URL or PUBLIC_BASE_URL must be set. "
                "No localhost fallback is allowed to prevent misconfigured deployments."
            )
        self.base_url = str(base_url).rstrip("/")
        # Reuse the app's auth path if required
        self.headers = headers or self._default_headers()

    def _default_headers(self) -> Dict[str, str]:
        h: Dict[str, str] = {}
        if os.getenv("FM_INTEL_AUTHORIZATION"):  # optional bearer token
            h["Authorization"] = os.getenv(
                "FM_INTEL_AUTHORIZATION"
            )  # e.g. "Bearer xxx"
        if os.getenv("FM_INTEL_API_KEY"):  # optional X-API-Key (your app accepts this)
            h["X-API-Key"] = os.getenv("FM_INTEL_API_KEY")
        if os.getenv("FM_INTEL_ACCOUNT_ID"):
            h["X-Account-Id"] = os.getenv("FM_INTEL_ACCOUNT_ID")
        return h

    async def auto_configure(
        self,
        df_source: Any,
        df_reference: Any,
        *,
        goal: str = "dedupe",
        sample_rows: int = 5000,
        exclude_personal_domains: bool = True,
        max_block_size: int = 25_000,
    ) -> Dict[str, Any]:
        """Ask the Intelligence API for a full config bundle (send only schema/shape)."""

        def _schema(df) -> List[str]:
            # Handle pandas DataFrame
            if hasattr(df, "columns"):
                return df.columns.tolist()
            # Handle list of dicts
            elif isinstance(df, list) and len(df) > 0 and isinstance(df[0], dict):
                return list(df[0].keys())
            return []

        source_schema = _schema(df_source)
        ref_schema = _schema(df_reference)

        # Debug logging
        log.info(f"Auto-configure called with goal={goal}")
        log.info(f"Source schema: {source_schema}")
        log.info(f"Reference schema: {ref_schema}")

        payload: Dict[str, Any] = {
            "goal": goal,
            "source": {"sample_rows": sample_rows, "schema": source_schema},
            "reference": {"sample_rows": sample_rows, "schema": ref_schema},
            "constraints": {
                "exclude_personal_domains": bool(exclude_personal_domains),
                "max_block_size": int(max_block_size),
            },
        }

        # Optionally short-circuit HTTP if you wire an in-process service later.
        url = f"{self.base_url}/api/v2/intelligence/auto-configure"
        log.debug(f"Calling Intelligence API at: {url}")

        # Build headers with auth
        headers = {}
        api_key = os.getenv("FM_INTEL_API_KEY")
        acct_id = os.getenv("FM_INTEL_ACCOUNT_ID")
        auth = os.getenv("FM_INTEL_AUTHORIZATION")
        if api_key:
            headers["X-API-Key"] = api_key
        if acct_id:
            headers["X-Account-Id"] = acct_id
        if auth:
            headers["Authorization"] = auth

        try:
            async with httpx.AsyncClient(timeout=30.0) as client:
                log.debug(f"Sending payload to Intelligence API: {payload}")
                r = await client.post(url, json=payload, headers=headers)

                if r.status_code == 404:
                    log.error("Intelligence API endpoint not found (404)")
                    raise RuntimeError(
                        "Intelligence API not available at /api/v2/intelligence/auto-configure"
                    )

                if r.status_code != 200:
                    log.error(
                        f"Intelligence API returned status {r.status_code}: {r.text}"
                    )

                r.raise_for_status()
                cfg = r.json()

                log.info(f"Intelligence API response: {cfg}")

                if not isinstance(cfg, dict):
                    log.error(
                        f"Invalid response type from Intelligence API: {type(cfg)}"
                    )
                    raise RuntimeError(
                        "Invalid response from Intelligence API (expected JSON object)."
                    )

                if "intelligence_config" in cfg:
                    config_payload = cfg.get("intelligence_config") or {}
                    if not isinstance(config_payload, dict):
                        log.error(
                            f"Expected intelligence_config to be dict, got {type(config_payload)}"
                        )
                        config_payload = {}
                    config = dict(config_payload)
                    mapping_warnings = cfg.get("mapping_warnings")
                    if isinstance(mapping_warnings, list):
                        config["mapping_warnings"] = mapping_warnings
                    config_hash = cfg.get("config_hash")
                    if config_hash:
                        config["config_hash"] = config_hash
                        config.setdefault("hash", config_hash)
                    for meta_key in (
                        "correlation_id",
                        "optimization_goal",
                        "estimated_processing_time_seconds",
                        "warnings",
                        "learning_metadata",
                    ):
                        if cfg.get(meta_key) is not None and meta_key not in config:
                            config[meta_key] = cfg[meta_key]
                    maps = config.get("mappings")
                    if isinstance(maps, list):
                        config["field_mappings"] = maps
                    elif isinstance(config.get("field_mappings"), list):
                        config["mappings"] = config["field_mappings"]
                    else:
                        config["mappings"] = []
                        config["field_mappings"] = []
                    cfg = config
                else:
                    maps = cfg.get("mappings")
                    if isinstance(maps, list):
                        cfg.setdefault("field_mappings", maps)
                    elif isinstance(cfg.get("field_mappings"), list):
                        cfg.setdefault("mappings", cfg.get("field_mappings"))

                if not cfg or not cfg.get("field_mappings"):
                    log.warning(
                        f"Intelligence API returned empty or incomplete config: {cfg}"
                    )

                return cfg

        except httpx.ConnectError as e:
            log.error(f"Failed to connect to Intelligence API at {url}: {e}")
            return {}

    async def get_policy(
        self, *, account_id: Optional[str] = None, segment: Optional[str] = None
    ) -> Dict[str, Any]:
        """Fetch current thresholds/policy for account/segment."""
        url = f"{self.base_url}/api/v2/intelligence/policy"
        params = {}
        if segment:
            params["segment"] = segment

        headers = self._default_headers()
        if account_id:
            headers["X-Account-Id"] = str(account_id)

        try:
            async with httpx.AsyncClient(timeout=15.0) as client:
                r = await client.get(url, params=params, headers=headers)
                r.raise_for_status()
                return r.json()
        except httpx.TimeoutException as e:
            log.error(f"Intelligence.get_policy timeout: {e}")
            return {"suggest": 0.70, "min_gap": 0.10, "auto_link": 0.80, "weights": {}}
        except httpx.HTTPError as e:
            log.error(f"Intelligence.get_policy HTTP error: {e}")
            return {"suggest": 0.70, "min_gap": 0.10, "auto_link": 0.80, "weights": {}}
        except Exception as e:
            log.error(f"Intelligence.get_policy unexpected error: {e}")
            return {"suggest": 0.70, "min_gap": 0.10, "auto_link": 0.80, "weights": {}}
