"""Configuration loading from YAML, environment variables, and CLI flags."""

from __future__ import annotations

import os
from pathlib import Path

import yaml

ENV_PREFIX = "CYPHER_COMPLIANCE_"

# Mapping of env var suffixes to config paths
ENV_MAP = {
    "ADAPTER": ("database", "adapter"),
    "HOST": ("database", "host"),
    "PORT": ("database", "port"),
    "USERNAME": ("database", "auth", "username"),
    "PASSWORD": ("database", "auth", "password"),
    "NAME": ("database", "name"),
    "QUERY_TIMEOUT": ("database", "query_timeout"),
}


def _set_nested(d: dict, keys: tuple, value):
    """Set a value in a nested dict given a tuple of keys."""
    for key in keys[:-1]:
        d = d.setdefault(key, {})
    d[keys[-1]] = value


def _get_nested(d: dict, keys: tuple, default=None):
    """Get a value from a nested dict given a tuple of keys."""
    for key in keys:
        if not isinstance(d, dict):
            return default
        d = d.get(key, default)
        if d is default:
            return default
    return d


def load_config(
    config_path: str | Path | None = None,
    cli_overrides: dict | None = None,
) -> dict:
    """Load a single-database config from env vars and/or CLI flags.

    This is used for the inline-flags path (``run -a bolt --host ...``)
    and internally by the runner. Config files should use
    :func:`load_multi_config` instead.

    Args:
        config_path: Path to config.yaml (legacy single-database format).
        cli_overrides: Dict of CLI flag overrides (flat keys like "adapter", "host", etc.)

    Returns:
        Configuration dict with a ``database`` key.

    Raises:
        ValueError: If no adapter is specified anywhere.
    """
    config = {}

    # 1. Load from YAML file
    if config_path:
        path = Path(config_path)
        if not path.exists():
            raise FileNotFoundError(
                f"Config file not found: {path}\n"
                f"Create a config.yaml or use CLI flags (e.g. --adapter bolt --host localhost)."
            )
        with open(path) as f:
            config = yaml.safe_load(f) or {}

    # 2. Apply environment variable overrides
    for suffix, keys in ENV_MAP.items():
        env_var = f"{ENV_PREFIX}{suffix}"
        value = os.environ.get(env_var)
        if value is not None:
            # Convert port to int
            if suffix == "PORT":
                value = int(value)
            elif suffix == "QUERY_TIMEOUT":
                value = int(value)
            _set_nested(config, keys, value)

    # 3. Apply CLI flag overrides
    if cli_overrides:
        cli_to_keys = {
            "adapter": ("database", "adapter"),
            "host": ("database", "host"),
            "port": ("database", "port"),
            "username": ("database", "auth", "username"),
            "password": ("database", "auth", "password"),
            "timeout": ("database", "query_timeout"),
        }
        for key, keys in cli_to_keys.items():
            if key in cli_overrides and cli_overrides[key] is not None:
                _set_nested(config, keys, cli_overrides[key])

    # Validate
    db = config.get("database", {})
    if not db.get("adapter"):
        raise ValueError(
            "No adapter specified. Provide via config.yaml, "
            f"{ENV_PREFIX}ADAPTER env var, or --adapter flag."
        )

    # Default database name to adapter name if not set
    if not db.get("name"):
        db["name"] = db["adapter"]

    return config


def load_multi_config(config_path: str | Path) -> list[dict]:
    """Load database configs from a YAML file.

    The config file must use the ``databases`` list format::

        databases:
          - name: Neo4j
            adapter: bolt
            host: localhost
            port: 7687

    Returns a list of config dicts, each with a ``database`` key,
    suitable for passing to ``run_compliance(config=...)``.
    """
    path = Path(config_path)
    if not path.exists():
        raise FileNotFoundError(
            f"Config file not found: {path}\n"
            f"Create a config.yaml with a 'databases' list."
        )

    with open(path) as f:
        raw = yaml.safe_load(f) or {}

    if "databases" not in raw or not isinstance(raw["databases"], list):
        raise ValueError(
            f"Config file must contain a 'databases' list. "
            f"Got top-level keys: {list(raw.keys())}"
        )

    if not raw["databases"]:
        raise ValueError("'databases' list is empty")

    configs = []
    for db_entry in raw["databases"]:
        db = dict(db_entry)
        if not db.get("adapter"):
            raise ValueError(
                f"Database entry missing 'adapter': {db.get('name', '(unnamed)')}"
            )
        if not db.get("name"):
            db["name"] = db["adapter"]
        configs.append({"database": db})
    return configs
