"""Run manifest helpers."""

from __future__ import annotations

import platform
import sys
from dataclasses import asdict
from datetime import datetime, timezone
from importlib import metadata
from pathlib import Path
from typing import Any

import numpy as np

from babappa.config import BABAPPAConfig
from babappa.utils.fs import write_json
from babappa.version import __version__


def _to_jsonable(value: Any) -> Any:
    if value is None or isinstance(value, (str, int, float, bool)):
        return value
    if isinstance(value, dict):
        return {str(k): _to_jsonable(v) for k, v in value.items() if not callable(v)}
    if isinstance(value, (list, tuple, set)):
        return [_to_jsonable(v) for v in value]
    if callable(value):
        return repr(value)
    return str(value)


def _package_version(name: str) -> str | None:
    try:
        return metadata.version(name)
    except metadata.PackageNotFoundError:
        return None


def build_manifest(
    command: str,
    config: BABAPPAConfig,
    seed: int,
    args: dict[str, Any] | None = None,
    extras: dict[str, Any] | None = None,
) -> dict[str, Any]:
    manifest: dict[str, Any] = {
        "command": command,
        "timestamp_utc": datetime.now(timezone.utc).isoformat(),
        "version": __version__,
        "python": sys.version,
        "platform": {
            "system": platform.system(),
            "release": platform.release(),
            "machine": platform.machine(),
        },
        "dependencies": {
            "numpy": np.__version__,
            "pandas": _package_version("pandas"),
            "scipy": _package_version("scipy"),
            "matplotlib": _package_version("matplotlib"),
            "PyYAML": _package_version("PyYAML"),
            "jinja2": _package_version("jinja2"),
        },
        "seed": int(seed),
        "config": asdict(config),
    }
    if args is not None:
        manifest["args"] = _to_jsonable(args)
    if extras:
        manifest["extras"] = _to_jsonable(extras)
    return manifest


def write_manifest(path: str | Path, manifest: dict[str, Any]) -> None:
    write_json(path, manifest)
