"""Load a :class:`ServiceSpec` from a TOML spec file.

The spec file is the version-controlled source of truth for one service. A
``cloud_init_file`` key (relative to the spec) is read into the spec's
``cloud_init`` user-data; everything else maps 1:1 onto ``ServiceSpec`` fields
(unknown keys are rejected — ``extra="forbid"``).
"""

from __future__ import annotations

import tomllib
from pathlib import Path
from typing import TYPE_CHECKING

from pydantic import ValidationError

from mgf.cloud_provision.exceptions import SpecError
from mgf.cloud_provision.models import ServiceSpec

if TYPE_CHECKING:
    from collections.abc import Mapping

__all__ = ["load_spec"]


def load_spec(path: str | Path) -> ServiceSpec:
    """Parse + validate a service spec TOML file into a :class:`ServiceSpec`.

    Raises :class:`SpecError` on a missing file, invalid TOML, a missing
    ``cloud_init_file``, or a field that doesn't validate.
    """
    spec_path = Path(path)
    try:
        raw: dict[str, object] = tomllib.loads(spec_path.read_text(encoding="utf-8"))
    except FileNotFoundError as exc:
        raise SpecError(f"spec file not found: {spec_path}") from exc
    except tomllib.TOMLDecodeError as exc:
        raise SpecError(f"invalid TOML in {spec_path}: {exc}") from exc

    ci_file = raw.pop("cloud_init_file", None)
    if ci_file is not None:
        ci_path = spec_path.parent / str(ci_file)
        try:
            raw["cloud_init"] = ci_path.read_text(encoding="utf-8")
        except FileNotFoundError as exc:
            raise SpecError(f"cloud_init_file not found: {ci_path}") from exc

    return _build(raw, spec_path)


def _build(raw: Mapping[str, object], spec_path: Path) -> ServiceSpec:
    try:
        return ServiceSpec.model_validate(dict(raw))
    except ValidationError as exc:
        raise SpecError(f"invalid ServiceSpec in {spec_path}: {exc}") from exc
