"""Load, save, and resolve bug bounty program definitions."""

from pathlib import Path

import yaml
from pydantic import ValidationError

import noisegate.config as config
from noisegate.exceptions import ProgramParseError
from noisegate.models import Program

# Re-export so callers can do `from noisegate.program import _PROGRAMS_DIR`
# and get the same object as config._PROGRAMS_DIR at import time.
# Tests that monkeypatch config._PROGRAMS_DIR should also patch program._PROGRAMS_DIR.
_PROGRAMS_DIR = config._PROGRAMS_DIR


def resolve_program(name_or_path: str) -> Path:
    """Return a Path for the given name or explicit path.

    'acme' → ~/.config/noisegate/programs/acme.yaml
    Explicit paths (absolute or containing a path separator) pass through as-is.
    """
    p = Path(name_or_path)
    if p.is_absolute() or "/" in name_or_path or "\\" in name_or_path:
        return p
    return config._PROGRAMS_DIR / f"{name_or_path}.yaml"


def load_program(path: Path) -> Program:
    """Parse a YAML program file into a Program model.

    Raises ProgramParseError on bad YAML or validation failure.
    """
    try:
        raw = yaml.safe_load(path.read_text())
    except yaml.YAMLError as exc:
        raise ProgramParseError(path, str(exc)) from exc
    except OSError as exc:
        raise ProgramParseError(path, str(exc)) from exc

    try:
        return Program.model_validate(raw)
    except ValidationError as exc:
        raise ProgramParseError(path, str(exc)) from exc


def save_program(program: Program, path: Path) -> None:
    """Serialize a Program to YAML and write it to path (creates parent dirs)."""
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(yaml.dump(program.model_dump(), sort_keys=False, allow_unicode=True))


def list_programs() -> list[str]:
    """Return names of all saved programs (alphabetical)."""
    if not config._PROGRAMS_DIR.exists():
        return []
    return sorted(p.stem for p in config._PROGRAMS_DIR.glob("*.yaml"))


def remove_program(name: str) -> None:
    """Delete a named program file. Raises ProgramNotFoundError if it doesn't exist."""
    from noisegate.exceptions import ProgramNotFoundError
    path = config._PROGRAMS_DIR / f"{name}.yaml"
    if not path.exists():
        raise ProgramNotFoundError(name)
    path.unlink()
    cfg = config._read_config()
    if cfg.get("default_program") == name:
        del cfg["default_program"]
        config._write_config(cfg)
