"""ConfigLoader — YAML/JSON config loading with env-var substitution."""

from __future__ import annotations

import json
import os
import re
from pathlib import Path
from typing import Any

import yaml

from pyfortis.errors import ConfigurationError

_ENV_PATTERN = re.compile(r"\$\{([^}]+)\}")


def _substitute_env(value: str) -> str:
    """Substitute ${VAR}, ${VAR:-default}, ${VAR:?error} patterns."""

    def _replace(match: re.Match) -> str:
        expr = match.group(1)

        # ${VAR:?error} — required
        if ":?" in expr:
            var_name, error_msg = expr.split(":?", 1)
            val = os.environ.get(var_name.strip())
            if val is None:
                raise ConfigurationError(
                    error_msg.strip() or f"Required env var '{var_name.strip()}' not set"
                )
            return val

        # ${VAR:-default}
        if ":-" in expr:
            var_name, default = expr.split(":-", 1)
            return os.environ.get(var_name.strip(), default.strip())

        # ${VAR}
        var_name = expr.strip()
        val = os.environ.get(var_name)
        if val is None:
            return match.group(0)  # Leave as-is if not found
        return val

    return _ENV_PATTERN.sub(_replace, value)


def _walk_substitute(obj: Any) -> Any:
    """Recursively substitute env vars in strings."""
    if isinstance(obj, str):
        return _substitute_env(obj)
    if isinstance(obj, dict):
        return {k: _walk_substitute(v) for k, v in obj.items()}
    if isinstance(obj, list):
        return [_walk_substitute(v) for v in obj]
    return obj


class ConfigLoader:
    """Load risk config from YAML or JSON files."""

    @staticmethod
    def load(path: str | Path) -> dict[str, Any]:
        """Load and return raw config dict with env-var substitution."""
        path = Path(path)
        if not path.exists():
            raise ConfigurationError(
                f"Config file not found: {path}",
                context={"path": str(path)},
            )

        text = path.read_text(encoding="utf-8")

        if path.suffix in (".yaml", ".yml"):
            raw = yaml.safe_load(text)
        elif path.suffix == ".json":
            raw = json.loads(text)
        else:
            raise ConfigurationError(
                f"Unsupported config format: {path.suffix}",
                context={"path": str(path), "suffix": path.suffix},
            )

        if not isinstance(raw, dict):
            raise ConfigurationError(
                "Config must be a mapping at the top level",
                context={"path": str(path), "type": type(raw).__name__},
            )

        return _walk_substitute(raw)

    @staticmethod
    def load_string(text: str, *, fmt: str = "yaml") -> dict[str, Any]:
        """Load config from a string."""
        if fmt == "yaml":
            raw = yaml.safe_load(text)
        elif fmt == "json":
            raw = json.loads(text)
        else:
            raise ConfigurationError(f"Unsupported format: {fmt}")

        if not isinstance(raw, dict):
            raise ConfigurationError("Config must be a mapping at the top level")

        return _walk_substitute(raw)
