"""Provenance inventory and immutable manifest helpers for OID v2."""
from __future__ import annotations

import hashlib
import json
import os
import platform
import subprocess
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Iterable, Mapping

import numpy as np

from CCD_analysis.utils.run_contract import (
    ContractError,
    check_execution_environment,
    sha256_file,
)

from .contracts import LOGICAL_GPU, PHYSICAL_GPU


def _git(repo: Path, *args: str) -> str:
    return subprocess.check_output(["git", *args], cwd=repo, text=True).strip()


def git_state(repo: str | Path) -> dict[str, Any]:
    root = Path(repo).resolve()
    status = _git(root, "status", "--porcelain=v1")
    return {"commit": _git(root, "rev-parse", "HEAD"), "dirty": bool(status), "status": status.splitlines()}


def npz_schema(path: str | Path) -> dict[str, Any]:
    result: dict[str, Any] = {}
    with np.load(path, mmap_mode="r", allow_pickle=False) as archive:
        for key in archive.files:
            array = archive[key]
            result[key] = {"shape": list(array.shape), "dtype": str(array.dtype), "nbytes": int(array.nbytes)}
    return result


def inventory_file(path: str | Path, *, base: str | Path | None = None, inspect_npz: bool = True) -> dict[str, Any]:
    item = Path(path).resolve()
    stat = item.stat()
    relative = str(item.relative_to(Path(base).resolve())) if base is not None else str(item)
    record: dict[str, Any] = {
        "path": relative,
        "size_bytes": stat.st_size,
        "mtime_ns": stat.st_mtime_ns,
        "sha256": sha256_file(item),
    }
    if inspect_npz and item.suffix == ".npz":
        try:
            record["npz"] = npz_schema(item)
        except (OSError, ValueError, KeyError) as exc:
            record["npz_error"] = f"{type(exc).__name__}: {exc}"
    return record


def build_inventory(paths: Iterable[str | Path], *, base: str | Path, labels: Mapping[str, str] | None = None) -> dict[str, Any]:
    root = Path(base).resolve()
    records = []
    for path in sorted({Path(p).resolve() for p in paths}):
        record = inventory_file(path, base=root)
        if labels and str(path) in labels:
            record["role"] = labels[str(path)]
        records.append(record)
    return {
        "schema_version": 1,
        "created_utc": datetime.now(timezone.utc).isoformat(),
        "base": str(root),
        "files": records,
        "file_count": len(records),
        "total_size_bytes": sum(item["size_bytes"] for item in records),
    }


def environment_record(*, repo: str | Path, command: list[str], device: int | None) -> dict[str, Any]:
    conda_env = os.environ.get("CONDA_DEFAULT_ENV")
    return {
        "created_utc": datetime.now(timezone.utc).isoformat(),
        "command": command,
        "cwd": os.getcwd(),
        "conda_env": conda_env,
        "required_conda_env": "pycuda_3_10",
        "device_physical": device,
        "device_logical": LOGICAL_GPU if device == PHYSICAL_GPU else None,
        "cuda_visible_devices": os.environ.get("CUDA_VISIBLE_DEVICES"),
        "python": platform.python_version(),
        "platform": platform.platform(),
        "git": git_state(repo),
    }


def validate_execution_environment(*, device: int | None, require_gpu: bool) -> dict[str, Any]:
    """Apply OID physical-GPU policy 2 without importing or initializing CUDA."""
    if require_gpu:
        if device != PHYSICAL_GPU:
            raise ContractError(f"OID CFD CLI must record physical --device {PHYSICAL_GPU}")
        return check_execution_environment(physical_gpu=PHYSICAL_GPU)
    if os.environ.get("CONDA_DEFAULT_ENV") != "pycuda_3_10":
        raise ContractError("OID v2 must run inside conda environment pycuda_3_10")
    return {"conda_env": "pycuda_3_10", "physical_gpu": None, "logical_device": None}


def read_json(path: str | Path) -> Any:
    with Path(path).open(encoding="utf-8") as stream:
        return json.load(stream)
