"""JSON serialisation for PerilVine models.

We wrap pyvinecopulib's native JSON serialisation with our own envelope
that carries the peril names, fitting metadata, and library version. This
means a serialised model is self-describing — you don't need to remember
which columns were used.

Format::

    {
        "version": "0.1.0",
        "perils": ["flood", "subsidence", "storm", "eow"],
        "family_set": "parametric",
        "selection_criterion": "bic",
        "tree_criterion": "tau",
        "trunc_lvl": 3,
        "vine_type": "rvine",
        "vine_json": "<pyvinecopulib native JSON string>"
    }
"""

from __future__ import annotations

import json
from typing import Any

from importlib.metadata import version, PackageNotFoundError

try:
    _VERSION = version("insurance-copula")
except PackageNotFoundError:
    _VERSION = "dev"


def serialise_vine(
    vine: object,
    perils: list[str],
    family_set: str,
    selection_criterion: str,
    tree_criterion: str,
    trunc_lvl: int | None,
    vine_type: str,
) -> str:
    """Serialise a fitted Vinecop to a JSON string.

    Parameters
    ----------
    vine:
        Fitted ``pyvinecopulib.Vinecop`` instance.
    perils:
        Peril names used during fitting.
    family_set:
        Family set string (e.g. ``'parametric'``).
    selection_criterion:
        Selection criterion string (e.g. ``'bic'``).
    tree_criterion:
        Tree criterion string (e.g. ``'tau'``).
    trunc_lvl:
        Truncation level (None for full vine).
    vine_type:
        Vine type string (e.g. ``'rvine'``).

    Returns
    -------
    str
        JSON string.
    """
    envelope: dict[str, Any] = {
        "version": _VERSION,
        "perils": perils,
        "family_set": family_set,
        "selection_criterion": selection_criterion,
        "tree_criterion": tree_criterion,
        "trunc_lvl": trunc_lvl,
        "vine_type": vine_type,
        "vine_json": vine.to_json(),
    }
    return json.dumps(envelope, indent=2)


def deserialise_vine(s: str) -> tuple[object, dict[str, Any]]:
    """Deserialise a PerilVine JSON string.

    Parameters
    ----------
    s:
        JSON string produced by :func:`serialise_vine`.

    Returns
    -------
    tuple[Vinecop, dict]
        The restored ``pyvinecopulib.Vinecop`` and the envelope metadata
        dict (perils, family_set, etc.).

    Raises
    ------
    ImportError
        If pyvinecopulib is not installed.
    ValueError
        If the JSON is malformed or missing required keys.
    """
    try:
        import pyvinecopulib as pv  # type: ignore[import-untyped]
    except ImportError as exc:
        raise ImportError(
            "pyvinecopulib is required for deserialisation. "
            "Install it with: pip install pyvinecopulib"
        ) from exc

    try:
        envelope = json.loads(s)
    except json.JSONDecodeError as exc:
        raise ValueError(f"Invalid JSON: {exc}") from exc

    required_keys = {
        "perils", "family_set", "selection_criterion",
        "tree_criterion", "trunc_lvl", "vine_type", "vine_json",
    }
    missing = required_keys - set(envelope.keys())
    if missing:
        raise ValueError(
            f"Serialised PerilVine is missing keys: {missing}"
        )

    vine = pv.Vinecop.from_json(envelope["vine_json"])

    metadata = {
        "perils": envelope["perils"],
        "family_set": envelope["family_set"],
        "selection_criterion": envelope["selection_criterion"],
        "tree_criterion": envelope["tree_criterion"],
        "trunc_lvl": envelope["trunc_lvl"],
        "vine_type": envelope["vine_type"],
        "version": envelope.get("version", "unknown"),
    }
    return vine, metadata
