#!/usr/bin/env python3
"""
Deterministic CPU backend with measured latency/energy.
Replaces mocked decoder with portable workload.
"""
import os
import time
import hashlib
from dataclasses import dataclass

try:
    import psutil  # optional
except Exception:
    psutil = None

# Optional RAPL
_RAPL = "/sys/class/powercap/intel-rapl:0/energy_uj"


@dataclass
class Perf:
    wall_ms: float
    cpu_ms: float
    energy_mJ: float
    energy_source: str


def _read_rapl_mJ():
    try:
        with open(_RAPL, "r") as f:
            return int(f.read().strip()) / 1000.0  # uJ → mJ
    except Exception:
        return None


def _hash_cycles(seed: bytes, cycles: int) -> bytes:
    h = seed
    for _ in range(cycles):
        h = hashlib.sha256(h).digest()
    return h


def run(prompt: str, phi0_deg: float | None, max_tokens: int) -> tuple[str, Perf]:
    """
    Deterministic CPU workload bound to phi0 and max_tokens.

    Args:
        prompt: Input text (first 1024 bytes used as seed)
        phi0_deg: Basin control angle (adds work proportional to value)
        max_tokens: Token count (adds work proportional to count)

    Returns:
        (content, perf) where content is deterministic hex string
    """
    # Bind work to phi0 & max_tokens deterministically
    tcycles = max(64, min(4096, int((max_tokens or 128) * 8)))
    pcycles = 0 if phi0_deg is None else int((abs(phi0_deg) % 360) * 4)

    seed = (prompt or "").encode("utf-8")[:1024]
    seed = seed or b"\x00"
    cycles = tcycles + pcycles

    # Pin threads (keeps variance down if BLAS present)
    os.environ.setdefault("OMP_NUM_THREADS", "1")
    os.environ.setdefault("MKL_NUM_THREADS", "1")

    # Energy start
    e0 = _read_rapl_mJ()
    c0 = time.process_time()
    t0 = time.perf_counter()

    # Work unit: K rounds of SHA-256; deterministic & portable
    out = _hash_cycles(seed, cycles)

    # End measures
    t1 = time.perf_counter()
    c1 = time.process_time()
    e1 = _read_rapl_mJ()

    wall_ms = (t1 - t0) * 1000.0
    cpu_ms = (c1 - c0) * 1000.0

    if e0 is not None and e1 is not None:
        energy_mJ = max(0.0, e1 - e0)
        source = "rapl"
    else:
        # Crude estimate: cpu_ms * TDP / 1000
        tdp_w = float(os.environ.get("FLOWTORCH_CPU_TDP_W", "28"))
        energy_mJ = max(0.0, cpu_ms * tdp_w)
        source = "estimate"

    # Turn 32 bytes into readable ASCII content (deterministic)
    content = out.hex()

    return content, Perf(wall_ms, cpu_ms, energy_mJ, source)
