"""Small, dependency-free helpers shared by the P0 vLLM extensions."""

from __future__ import annotations

import hashlib
import json
import os
import threading
import time
from pathlib import Path
from typing import Any


_LOCK = threading.Lock()
_POLICY: dict[str, Any] | None = None


def _canonical(value: Any) -> str:
    return json.dumps(value, ensure_ascii=True, separators=(",", ":"), sort_keys=True)


def digest(value: Any) -> str:
    return hashlib.blake2b(_canonical(value).encode("utf-8"), digest_size=16).hexdigest()


def policy() -> dict[str, Any]:
    global _POLICY
    if _POLICY is not None:
        return _POLICY
    path_value = os.environ.get("COLLECTIVESPEC_P0_POLICY_PATH")
    if not path_value:
        raise RuntimeError("COLLECTIVESPEC_P0_POLICY_PATH is required for P0")
    path = Path(path_value)
    value = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(value, dict):
        raise RuntimeError("P0 policy must be a JSON object")
    max_k = value.get("max_k")
    seed = value.get("seed")
    kind = value.get("policy")
    if isinstance(max_k, bool) or not isinstance(max_k, int) or max_k < 0:
        raise RuntimeError("P0 policy max_k must be a non-negative integer")
    if isinstance(seed, bool) or not isinstance(seed, int):
        raise RuntimeError("P0 policy seed must be an integer")
    if kind not in {"blake2b-request-static-k", "constant-k"}:
        raise RuntimeError(f"Unsupported P0 policy: {kind!r}")
    if kind == "constant-k":
        constant_k = value.get("constant_k")
        if isinstance(constant_k, bool) or not isinstance(constant_k, int):
            raise RuntimeError("constant-k policy requires integer constant_k")
        if not 0 <= constant_k <= max_k:
            raise RuntimeError("constant_k must be within [0, max_k]")
    _POLICY = value
    return value


def requested_k(request_id: str, candidate_count: int) -> int:
    value = policy()
    max_k = int(value["max_k"])
    if candidate_count < 0:
        raise RuntimeError("candidate_count must be non-negative")
    if value["policy"] == "constant-k":
        chosen = int(value["constant_k"])
    else:
        material = f"{value['seed']}|{request_id}".encode("utf-8")
        chosen = int.from_bytes(hashlib.blake2b(material, digest_size=8).digest(), "big") % (max_k + 1)
    return min(chosen, candidate_count)


def histogram(values: list[int]) -> dict[str, int]:
    counts: dict[str, int] = {}
    for value in values:
        key = str(int(value))
        counts[key] = counts.get(key, 0) + 1
    return dict(sorted(counts.items(), key=lambda item: int(item[0])))


def _jsonable(value: Any) -> Any:
    if value is None or isinstance(value, (str, int, float, bool)):
        return value
    if isinstance(value, (list, tuple)):
        return [_jsonable(item) for item in value]
    if isinstance(value, dict):
        return {str(key): _jsonable(item) for key, item in value.items()}
    tolist = getattr(value, "tolist", None)
    if callable(tolist):
        return _jsonable(tolist())
    item = getattr(value, "item", None)
    if callable(item):
        try:
            return _jsonable(item())
        except (TypeError, ValueError):
            pass
    return repr(value)


def dp_metadata_summary(metadata: Any) -> dict[str, Any] | None:
    if metadata is None:
        return None
    keys = (
        "num_pad_tokens",
        "num_tokens_across_dp",
        "num_tokens_per_rank",
        "num_reqs_per_rank",
        "is_prompt_batch",
        "all_prefill",
        "all_decode",
        "should_ubatch",
        "input_fits_in_drafter",
        "cudagraph_mode",
    )
    return {key: _jsonable(getattr(metadata, key, None)) for key in keys}


def plan_summary(scheduler_output: Any) -> dict[str, Any]:
    scheduled = getattr(scheduler_output, "num_scheduled_tokens", {}) or {}
    spec = getattr(scheduler_output, "scheduled_spec_decode_tokens", {}) or {}
    ordered = [
        {
            "request_id": str(request_id),
            "scheduled_tokens": int(tokens),
            "spec_k": len(spec.get(request_id, [])),
        }
        for request_id, tokens in scheduled.items()
    ]
    semantic = sorted(ordered, key=lambda item: item["request_id"])
    spec_ks = [item["spec_k"] for item in ordered if item["spec_k"] > 0]
    return {
        "ordered_plan_digest": digest(ordered),
        "semantic_plan_digest": digest(semantic),
        "request_count": len(ordered),
        "total_scheduled_rows": int(getattr(scheduler_output, "total_num_scheduled_tokens", 0)),
        "spec_request_count": len(spec),
        "spec_k_histogram": histogram(spec_ks),
        "verify_logical_rows": sum(1 + item["spec_k"] for item in ordered if item["spec_k"] > 0),
        "scheduled_dp_metadata": dp_metadata_summary(
            getattr(scheduler_output, "scheduled_dp_metadata", None)
        ),
    }


def log_event(role: str, event: str, **payload: Any) -> None:
    root_value = os.environ.get("COLLECTIVESPEC_P0_LOG_DIR")
    if not root_value:
        raise RuntimeError("COLLECTIVESPEC_P0_LOG_DIR is required for P0")
    root = Path(root_value)
    root.mkdir(parents=True, exist_ok=True)
    record = {
        "schema_version": "collectivespec-p0-log-v1",
        "event": event,
        "monotonic_s": time.monotonic(),
        "pid": os.getpid(),
        "policy_version": policy().get("version"),
        "role": role,
        **{key: _jsonable(value) for key, value in payload.items()},
    }
    path = root / f"{role}-pid{os.getpid()}.jsonl"
    with _LOCK, path.open("a", encoding="utf-8") as handle:
        handle.write(json.dumps(record, ensure_ascii=True, sort_keys=True) + "\n")
