"""Configuration file support — TOML-based, with precedence chain.

Precedence (highest wins):
  1. CLI flags
  2. Environment variables (CERTISIGMA_API_KEY, CERTISIGMA_BASE_URL)
  3. Project-level .census.toml (current directory or parent)
  4. User-level ~/.config/certisigma-census/config.toml
  5. Built-in defaults

Uses tomllib (Python 3.11+) or tomli (3.10 backport).
"""

from __future__ import annotations

import copy
import logging
import os
import sys
from pathlib import Path
from typing import Any, Optional

logger = logging.getLogger(__name__)

_DEFAULT_CONFIG: dict[str, Any] = {
    "api_key": "",
    "base_url": "https://api.certisigma.ch",
    "source": "",
    "manifest_dir": "",
    "scan": {
        "include": [],
        "exclude": [],
        "min_size": "",
        "max_size": "",
    },
    "watch": {
        "debounce": 2.0,
        "batch_interval": 30.0,
        "on_delete": "ignore",
        "polling": False,
    },
}

_TOML_TEMPLATE = """\
# CertiSigma Census configuration
# Docs: https://developers.certisigma.ch/sdk

# API key (or set CERTISIGMA_API_KEY env var)
# api_key = "cs_..."

# API base URL (default: https://api.certisigma.ch)
# base_url = "https://api.certisigma.ch"

# Default source label for attestations
# source = ""

[scan]
# include = ["*.pdf", "*.docx"]
# exclude = ["*.log", "*.tmp"]
# min_size = ""
# max_size = "1G"

[watch]
# debounce = 2.0
# batch_interval = 30.0
# on_delete = "ignore"
# polling = false
"""

_SENSITIVE_KEYS = frozenset({"api_key", "encryption_key"})
MAX_CONFIG_FILE_SIZE = 1 * 1024 * 1024  # 1 MB


def _load_toml(path: Path) -> dict[str, Any]:
    """Load a TOML file using tomllib (3.11+) or tomli (3.10)."""
    if sys.version_info >= (3, 11):
        import tomllib
    else:
        try:
            import tomli as tomllib  # type: ignore[no-redef,unused-ignore]
        except ImportError:
            logger.debug("tomli not installed, skipping config file %s", path)
            return {}

    try:
        file_size = path.stat().st_size
        if file_size > MAX_CONFIG_FILE_SIZE:
            logger.warning("Config file too large (%d bytes > %d): %s",
                           file_size, MAX_CONFIG_FILE_SIZE, path)
            return {}
        with path.open("rb") as f:
            return tomllib.load(f)
    except Exception as exc:
        logger.warning("Failed to parse %s: %s", path, exc)
        return {}


def _user_config_path() -> Path:
    """Return the user-level config path following XDG conventions."""
    xdg = os.environ.get("XDG_CONFIG_HOME")
    if xdg:
        base = Path(xdg)
    else:
        base = Path.home() / ".config"
    return base / "certisigma-census" / "config.toml"


def _find_project_config(start: Path | None = None) -> Optional[Path]:
    """Walk up from *start* looking for .census.toml."""
    current = (start or Path.cwd()).resolve()
    for _ in range(20):
        candidate = current / ".census.toml"
        if candidate.is_file():
            return candidate
        parent = current.parent
        if parent == current:
            break
        current = parent
    return None


def _deep_merge(base: dict, overlay: dict) -> dict:
    """Recursively merge *overlay* into *base* (non-mutating)."""
    merged = dict(base)
    for key, val in overlay.items():
        if key in merged and isinstance(merged[key], dict) and isinstance(val, dict):
            merged[key] = _deep_merge(merged[key], val)
        else:
            merged[key] = val
    return merged


def load_config(
    *,
    project_dir: Path | None = None,
    skip_user: bool = False,
    skip_project: bool = False,
) -> dict[str, Any]:
    """Load merged config from user + project files.

    Does NOT apply env vars or CLI flags — those are handled by the caller
    to maintain the precedence chain.
    """
    config = copy.deepcopy(_DEFAULT_CONFIG)

    if not skip_user:
        user_path = _user_config_path()
        if user_path.is_file():
            logger.debug("Loading user config from %s", user_path)
            user_data = _load_toml(user_path)
            config = _deep_merge(config, user_data)

    if not skip_project:
        project_path = _find_project_config(project_dir)
        if project_path is not None:
            logger.debug("Loading project config from %s", project_path)
            project_data = _load_toml(project_path)
            config = _deep_merge(config, project_data)

    return config


def mask_secret_tail(value: str) -> str:
    """Mask a secret for safe display — show only last 4 characters.

    Avoids ``first4...last4`` patterns that leak most of short tokens.
    """
    s = str(value)
    if len(s) <= 4:
        return "****"
    return "..." + s[-4:]


def mask_sensitive(config: dict[str, Any]) -> dict[str, Any]:
    """Return a copy with sensitive values masked for display."""
    masked: dict[str, Any] = {}
    for key, val in config.items():
        if isinstance(val, dict):
            masked[key] = mask_sensitive(val)
        elif key in _SENSITIVE_KEYS and val:
            masked[key] = mask_secret_tail(str(val))
        else:
            masked[key] = val
    return masked


def config_paths() -> dict[str, Optional[Path]]:
    """Return the paths Census looks for config files."""
    return {
        "user": _user_config_path(),
        "project": _find_project_config(),
    }


def init_config(path: Path) -> None:
    """Write a template config file at *path* (atomic, TOCTOU-safe).

    Raises ``FileExistsError`` if the file already exists.
    """
    import os

    path.parent.mkdir(parents=True, exist_ok=True)
    fd = os.open(str(path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            f.write(_TOML_TEMPLATE)
    except BaseException:
        try:
            os.unlink(str(path))
        except OSError as exc:
            logger.warning("Cannot remove partial config file %s: %s", path, exc)
        raise


def resolve_encryption_key(explicit_key: str | None = None) -> str:
    """Get encryption key from explicit argument, env var, or config.

    Precedence: explicit > CENSUS_ENCRYPTION_KEY env > config file.
    Raises ``ValueError`` if no key is found.
    """
    if explicit_key:
        return explicit_key
    env_key = os.environ.get("CENSUS_ENCRYPTION_KEY")
    if env_key:
        return env_key
    cfg = load_config()
    key = cfg.get("encryption_key", "")
    if not key:
        raise ValueError(
            "No encryption key provided. Pass --encryption-key, set "
            "CENSUS_ENCRYPTION_KEY env, or add 'encryption_key' to census config."
        )
    return str(key)
