#!/usr/bin/env python3
"""
rapl_backend.py
---------------
Minimal RAPL reader used by energy_model to optionally attach
real hardware energy measurements to receipts.

SAFE BEHAVIOR:
- If RAPL is unavailable, all functions degrade to No-Op.
"""

from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Optional
import time

# Default Intel RAPL path; can be adjusted if needed
RAPL_PATH = Path("/sys/class/powercap/intel-rapl:0/energy_uj")


def rapl_available() -> bool:
    """Return True if RAPL energy file exists and is readable."""
    try:
        return RAPL_PATH.exists() and RAPL_PATH.is_file()
    except Exception:
        return False


def read_energy_uj() -> Optional[int]:
    """
    Read current package energy in microjoules.
    Returns None if unavailable.
    """
    if not rapl_available():
        return None
    try:
        with RAPL_PATH.open("r") as f:
            txt = f.read().strip()
        return int(txt)
    except Exception:
        return None


@dataclass
class RaplWindow:
    """
    Context manager for measuring energy over a code region.

    Example:
        with RaplWindow() as rw:
            do_work()
        print(rw.delta_uj, rw.duration_s)
    """
    start_uj: Optional[int] = None
    end_uj: Optional[int] = None
    start_ts: float = 0.0
    end_ts: float = 0.0

    def __enter__(self) -> "RaplWindow":
        self.start_ts = time.time()
        self.start_uj = read_energy_uj()
        return self

    def __exit__(self, exc_type, exc, tb) -> None:
        self.end_ts = time.time()
        self.end_uj = read_energy_uj()

    @property
    def delta_uj(self) -> Optional[int]:
        if self.start_uj is None or self.end_uj is None:
            return None
        # Handle wraparound conservatively: if negative, just return None
        delta = self.end_uj - self.start_uj
        return delta if delta >= 0 else None

    @property
    def duration_s(self) -> float:
        return max(0.0, self.end_ts - self.start_ts)
