"""
Shared runtime config loaded from config.yaml (settings).
Secrets stay in .env (creds); this file holds only non-sensitive options.
"""

import os
from pathlib import Path

import yaml

_SCRIPT_DIR = Path(__file__).resolve().parent
# Default: same directory as this module (e.g. src/); can be overridden via set_config_path()
_CONFIG_PATH: Path | None = _SCRIPT_DIR / "config.yaml"
_CACHE: dict | None = None


def set_config_path(path: Path | None) -> None:
    """Set config file path (e.g. from CLI flag). Clears cache so next _load() reads from new path."""
    global _CONFIG_PATH, _CACHE
    _CONFIG_PATH = path
    _CACHE = None

# Defaults for optional keys (software_version and srl_yang_schema_build must be set in config)
_DEFAULTS = {
    "log_level": "INFO",
    "log_dir": "out/logs/eda-integrator.log",
    "stage_dir": "out/stage",
    "export_dir": "out/exported",
}


def _load() -> dict:
    global _CACHE
    if _CACHE is not None:
        return _CACHE
    path = _CONFIG_PATH if _CONFIG_PATH is not None else (_SCRIPT_DIR / "config.yaml")
    if not path.exists():
        _CACHE = dict(_DEFAULTS)
        return _CACHE
    try:
        with open(path) as f:
            _CACHE = yaml.safe_load(f) or {}
    except FileNotFoundError:
        raise ValueError(f"Config file not found: {path}") from None
    except yaml.YAMLError as e:
        raise ValueError(f"Invalid YAML in config: {path} — {e}") from e
    except OSError as e:
        raise ValueError(f"Could not read config: {path} — {e}") from e
    for k, v in _DEFAULTS.items():
        if k not in _CACHE:
            _CACHE[k] = v
    return _CACHE


def get_software_version() -> str:
    """Return software version from config.yaml. Required; no default."""
    v = _load().get("software_version")
    if v is None or not str(v).strip():
        raise ValueError(
            "config.yaml must set software_version (e.g. 24.10.1, 25.7.2). "
            "No default is applied."
        )
    return str(v).strip()


def get_srl_yang_schema_build() -> int:
    """Return SRL YANG schema profile zip build number from config.yaml (e.g. 492, 266). Required; no default."""
    v = _load().get("srl_yang_schema_build")
    if v is None:
        raise ValueError(
            "config.yaml must set srl_yang_schema_build (e.g. 492, 266). "
            "No default is applied."
        )
    try:
        return int(v)
    except (TypeError, ValueError) as e:
        raise ValueError(
            f"srl_yang_schema_build in config.yaml must be an integer (e.g. 492, 266). Got: {v!r}"
        ) from e


def get_log_level() -> str:
    """Return log level from config.yaml."""
    return str(_load().get("log_level", "INFO")).strip() or "INFO"


def get_log_dir() -> str | None:
    """Return log file path from config.yaml (log_dir), or None to disable file logging."""
    v = _load().get("log_dir", "out/logs/eda-integrator.log")
    if v is None:
        return None
    s = str(v).strip()
    return s or None


def get_stage_dir() -> str:
    """Return stage directory from config.yaml."""
    return str(_load().get("stage_dir", "out/stage")).strip() or "out/stage"


def get_export_dir() -> str:
    """Return export directory for kubectl export of EDA CRs from config.yaml."""
    return str(_load().get("export_dir", "out/exported")).strip() or "out/exported"


def get_edasaas_org_name() -> str | None:
    """Return organization name for EDA SaaS login (shared/MSP). Source of truth: env EDA_SAAS_ORG_NAME only."""
    v = os.environ.get("EDA_SAAS_ORG_NAME", "").strip()
    return v or None


def get_all_config() -> dict:
    """Return a copy of the full config dict from config.yaml (for display)."""
    return dict(_load())


def get_cluster_model() -> str:
    """
    Infer cluster type from env: if EDA_SAAS_ORG_NAME is set, cluster is treated as shared/MSP;
    otherwise the script assumes dedicated and uses Keycloak (KC_SECRET or KC_USER+KC_PASSWORD).
    """
    return "shared" if get_edasaas_org_name() else "dedicated"
