#!/usr/bin/env python3
"""True-signal resource gates for the Studio autonomy loop."""

from __future__ import annotations

import json
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from recoil.pipeline.tools.autonomy import constants, events


STATE_DIR = constants.STATE_DIR
BREAKER_DIR = constants.BREAKER_DIR
USAGE_LOG = constants.USAGE_LOG
MAX_BUILDS_PER_NIGHT = constants.MAX_BUILDS_PER_NIGHT
current_night_id = constants.current_night_id

BUILDS_DIR = STATE_DIR / "builds"


def _night_id(night_id: str | None = None) -> str:
    return night_id or current_night_id()


def _build_count_path(night_id: str) -> Path:
    return BUILDS_DIR / f"{night_id}.count"


def _breaker_path(night_id: str) -> Path:
    return BREAKER_DIR / f"{night_id}.breaker"


def _utc_now_iso() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def builds_tonight(night_id: str | None = None) -> int:
    """Return the cheap local build counter for the current autonomy night."""
    path = _build_count_path(_night_id(night_id))
    try:
        return max(0, int(path.read_text(encoding="utf-8").strip() or "0"))
    except FileNotFoundError:
        return 0
    except (OSError, ValueError):
        return 0


def record_build_started(night_id: str | None = None) -> int:
    """Increment and return the local build-start counter for a night."""
    resolved_night = _night_id(night_id)
    path = _build_count_path(resolved_night)
    path.parent.mkdir(parents=True, exist_ok=True)
    next_count = builds_tonight(resolved_night) + 1
    tmp_path = path.with_suffix(path.suffix + ".tmp")
    tmp_path.write_text(f"{next_count}\n", encoding="utf-8")
    tmp_path.replace(path)
    return next_count


def breaker_tripped(night_id: str | None = None) -> bool:
    """Return whether the per-night breaker sentinel exists."""
    return _breaker_path(_night_id(night_id)).exists()


def may_start_build(night_id: str | None = None) -> tuple[bool, str]:
    """Check only true resource signals before starting a build."""
    resolved_night = _night_id(night_id)
    if builds_tonight(resolved_night) >= MAX_BUILDS_PER_NIGHT:
        return False, "builds_cap"
    if breaker_tripped(resolved_night):
        return False, "night_breaker"
    return True, ""


def trip_breaker(night_id: str | None, reason: str) -> None:
    """Trip the night sentinel and emit the corresponding material event."""
    resolved_night = _night_id(night_id)
    path = _breaker_path(resolved_night)
    path.parent.mkdir(parents=True, exist_ok=True)
    path.touch()

    event_type = "rate_limited" if is_rate_limit_error(reason) else "cap_tripped"
    events.emit(event_type, night_id=resolved_night, reason=reason)


def is_rate_limit_error(text_or_returncode: Any) -> bool:
    """Recognize real CLI/API usage exhaustion and rate-limit failures."""
    if isinstance(text_or_returncode, int):
        return text_or_returncode == 429
    if text_or_returncode is None:
        return False

    text = str(text_or_returncode).casefold()
    if not text.strip():
        return False

    signatures = (
        "429",
        "too many requests",
        "rate limit",
        "rate-limit",
        "rate_limit",
        "rate limited",
        "rate-limited",
        "rate_limited",
        "ratelimit",
        "usage limit",
        "usage-limit",
        "usage_limit",
        "usage exhausted",
        "usage has been exhausted",
        "usage quota exceeded",
        "resource exhausted",
        "quota exceeded",
        "exceeded your current quota",
        "insufficient quota",
        "request limit exceeded",
        "tokens per minute",
        "requests per minute",
    )
    return any(signature in text for signature in signatures)


def _int_from_usage(usage: dict[str, Any], *keys: str) -> int:
    for key in keys:
        value = usage.get(key)
        if value is None:
            continue
        try:
            return max(0, int(value))
        except (TypeError, ValueError):
            continue
    return 0


def _informational_usd_equivalent(usage: dict[str, Any]) -> float:
    input_tokens = _int_from_usage(
        usage,
        "input_tokens",
        "prompt_tokens",
        "cache_creation_input_tokens",
    )
    cached_tokens = _int_from_usage(
        usage,
        "cached_input_tokens",
        "cache_read_input_tokens",
    )
    output_tokens = _int_from_usage(usage, "output_tokens", "completion_tokens")

    input_usd_per_million = 3.0
    cached_usd_per_million = 0.3
    output_usd_per_million = 15.0
    return round(
        (
            input_tokens * input_usd_per_million
            + cached_tokens * cached_usd_per_million
            + output_tokens * output_usd_per_million
        )
        / 1_000_000,
        6,
    )


def log_usage(run_id: str, phase: str, usage_dict: dict[str, Any]) -> None:
    """Append token usage telemetry for observability; never gate callers."""
    try:
        STATE_DIR.mkdir(parents=True, exist_ok=True)
        try:
            STATE_DIR.chmod(0o700)
        except OSError:
            pass
        USAGE_LOG.parent.mkdir(parents=True, exist_ok=True)
        usage = dict(usage_dict or {})
        row = {
            "schema": "autonomy.usage.v1",
            "ts": _utc_now_iso(),
            "night_id": current_night_id(),
            "run_id": run_id,
            "phase": phase,
            "usage": usage,
            "usd_equivalent_estimate": _informational_usd_equivalent(usage),
        }
        with USAGE_LOG.open("a", encoding="utf-8") as handle:
            handle.write(json.dumps(row, sort_keys=True, default=str) + "\n")
    except Exception:
        return None
    return None


class WallClock:
    """Small monotonic wall-clock watchdog helper."""

    def __init__(self, deadline_seconds: float):
        self.deadline_seconds = max(0.0, float(deadline_seconds))
        self.started_at = time.monotonic()

    def expired(self) -> bool:
        return time.monotonic() - self.started_at >= self.deadline_seconds
