from __future__ import annotations

from pathlib import Path
from typing import Dict, Union

import yaml
from sqlalchemy import create_engine, text

from .config import RootConfig
from .database import build_database_connection, ConnectionOutput
from .exceptions import (
    AuthMaterializationError,
    ConfigurationError,
    SecretProviderError,
    ValidationError,
)
from .llm import build_llm_config
from .secrets import build_secret_provider, SecretProvider


class ConnectionManager:
    """Primary interface for building connection credentials from secret-backed configuration."""

    def __init__(self, config: RootConfig):
        self.config = config
        self.secret_provider: SecretProvider = build_secret_provider(config.secret_store)

    @classmethod
    def from_yaml(cls, path: str) -> "ConnectionManager":
        """Load configuration from a YAML file."""
        data = yaml.safe_load(Path(path).read_text())
        config = RootConfig.parse_obj(data)
        return cls(config)

    @classmethod
    def from_dict(cls, config: Dict[str, object]) -> "ConnectionManager":
        """Load configuration from a dictionary."""
        parsed = RootConfig.parse_obj(config)
        return cls(parsed)

    def _get_connection_config(self, key: str):
        conn = self.config.get_connection(key)
        if not conn:
            raise ConfigurationError(f"Connection '{key}' not configured")
        return conn

    def get_database_url(self) -> ConnectionOutput:
        """Return SQLAlchemy-compatible database connection representation."""
        conn = self._get_connection_config("database")
        return build_database_connection(conn.auth_type, conn.secret_mapping, self.secret_provider)

    def get_llm_config(self) -> Dict[str, object]:
        """Return OpenAI SDK-compatible configuration for LLM usage."""
        conn = self._get_connection_config("llm")
        return build_llm_config(conn.auth_type, conn.secret_mapping, self.secret_provider)

    def get_embedding_config(self) -> Dict[str, object]:
        """Return OpenAI SDK-compatible configuration for embedding usage."""
        conn = self._get_connection_config("embedding")
        return build_llm_config(conn.auth_type, conn.secret_mapping, self.secret_provider)

    def validate(self, live: bool = True) -> Dict[str, Dict[str, str]]:
        """
        Validate configured connections and return status map.

        Args:
            live: when False, only checks materialization (no outbound connections).
        """
        results: Dict[str, Dict[str, str]] = {}
        # Probe secret store with the first available key to ensure reachability
        try:
            probe_key = None
            for conn in self.config.connections.values():
                if conn.secret_mapping:
                    probe_key = next(iter(conn.secret_mapping.values()))
                    break
            if probe_key:
                self.secret_provider.get(probe_key)
            results["secret_store"] = {"status": "ok", "message": "reachable"}
        except Exception as exc:
            results["secret_store"] = {"status": "error", "message": str(exc)}

        for key, validator in (
            ("database", self.validate_database),
            ("llm", self.validate_llm),
            ("embedding", self.validate_embedding),
        ):
            conn = self.config.get_connection(key)
            if not conn:
                results[key] = {"status": "error", "auth_type": "", "message": f"{key} not configured"}
                continue
            if not live:
                try:
                    # Only ensure we can materialize credentials
                    if key == "database":
                        self.get_database_url()
                    elif key == "llm":
                        self.get_llm_config()
                    elif key == "embedding":
                        self.get_embedding_config()
                    results[key] = {"status": "ok", "auth_type": conn.auth_type, "message": "materialization ok"}
                except Exception as exc:
                    results[key] = {"status": "error", "auth_type": conn.auth_type, "message": str(exc)}
                continue
            try:
                ok = validator()
                results[key] = {
                    "status": "ok" if ok else "error",
                    "auth_type": conn.auth_type,
                    "message": "ok" if ok else "validation failed",
                }
            except Exception as exc:
                results[key] = {"status": "error", "auth_type": conn.auth_type, "message": str(exc)}
        return results

    def validate_database(self) -> bool:
        """Attempt a SQLAlchemy connection and execute SELECT 1."""
        try:
            db = self.get_database_url()
        except (ConfigurationError, SecretProviderError, AuthMaterializationError) as exc:
            raise ValidationError(f"Database configuration invalid: {exc}") from exc

        try:
            if isinstance(db, dict):
                url = db["url"]
                connect_args = db.get("connect_args") or {}
                engine = create_engine(url, connect_args=connect_args)
            else:
                engine = create_engine(db)
            with engine.connect() as conn:
                conn.execute(text("SELECT 1"))
            engine.dispose()
            return True
        except Exception as exc:  # pragma: no cover - requires live DB
            raise ValidationError(f"Database validation failed: {exc}") from exc

    def validate_llm(self) -> bool:
        """Issue a minimal chat completion request to confirm connectivity."""
        try:
            config = self.get_llm_config()
        except (ConfigurationError, SecretProviderError, AuthMaterializationError) as exc:
            raise ValidationError(f"LLM configuration invalid: {exc}") from exc

        try:
            from openai import OpenAI  # type: ignore
        except ImportError as exc:  # pragma: no cover - optional dependency
            raise ValidationError("openai library is required for LLM validation") from exc

        try:
            client = OpenAI(**config)
            client.chat.completions.create(messages=[{"role": "user", "content": "ping"}], model=config.get("model"), max_tokens=1)
            return True
        except Exception as exc:  # pragma: no cover - requires live endpoint
            raise ValidationError(f"LLM validation failed: {exc}") from exc

    def validate_embedding(self) -> bool:
        """Issue a minimal embedding request to confirm connectivity."""
        try:
            config = self.get_embedding_config()
        except (ConfigurationError, SecretProviderError, AuthMaterializationError) as exc:
            raise ValidationError(f"Embedding configuration invalid: {exc}") from exc

        try:
            from openai import OpenAI  # type: ignore
        except ImportError as exc:  # pragma: no cover - optional dependency
            raise ValidationError("openai library is required for embedding validation") from exc

        try:
            client = OpenAI(**config)
            client.embeddings.create(input="ping", model=config.get("model"))
            return True
        except Exception as exc:  # pragma: no cover - requires live endpoint
            raise ValidationError(f"Embedding validation failed: {exc}") from exc
