"""Pure capacity decisions. No provider names, no hardcoded quotas."""

from __future__ import annotations

from src.core.provider_capacity.types import (
    BucketSnapshot,
    CapacityObservation,
    CapacityPolicy,
    ReserveDecision,
)

# Sentinel for "no TPM bound yet": admit by in-flight count only. Not a provider quota.
UNKNOWN_AVAILABLE = 2_147_483_647


def safe_capacity(limit: int | None, safety_fraction: float) -> int | None:
    if limit is None:
        return None
    return int(limit * safety_fraction)


def available_units(
    state: BucketSnapshot, policy: CapacityPolicy, now_ms: int
) -> tuple[int, str, int | None]:
    """Return (available, mode, safe_capacity). Never invents a provider limit.

    available = min(provider_remaining, safety_envelope) - local_reservations

    Provider remaining accounts for organization-scoped consumption outside
    this process. Local reserved accounts for P2HS in-flight work that may not
    yet be visible in provider remaining.
    """
    safe = safe_capacity(state.observed_limit, policy.safety_fraction)
    if _observation_usable(state, policy, now_ms):
        remaining = state.observed_remaining
        mode = "observed"
        if (
            state.reset_at_ms is not None
            and state.observed_limit is not None
            and state.reset_at_ms <= now_ms
        ):
            remaining = state.observed_limit
            mode = "reset_refilled"
        assert remaining is not None and safe is not None
        headroom = min(remaining, safe) - state.reserved
        return max(0, headroom), mode, safe
    if state.in_flight < policy.unknown_max_in_flight:
        return UNKNOWN_AVAILABLE, "unknown_fallback", safe
    return 0, "unknown_fallback", safe


def try_reserve(
    state: BucketSnapshot,
    *,
    demand: int,
    waiter_id: str,
    now_ms: int,
    policy: CapacityPolicy,
) -> ReserveDecision:
    available, mode, safe = available_units(state, policy, now_ms)
    waiters = list(state.waiters)
    in_queue = any(item[0] == waiter_id for item in waiters)
    if not in_queue:
        waiters.append((waiter_id, demand, now_ms))
    head_id = waiters[0][0]
    can_take = head_id == waiter_id and demand <= available
    if can_take:
        state.reserved += demand
        state.in_flight += 1
        state.waiters = [item for item in waiters if item[0] != waiter_id]
        return ReserveDecision(
            granted=True,
            queued=in_queue,
            available=available - demand,
            safe_capacity=safe,
            mode=mode,
            queue_depth=len(state.waiters),
            observed_limit=state.observed_limit,
            observed_remaining=state.observed_remaining,
        )
    state.waiters = waiters
    return ReserveDecision(
        granted=False,
        queued=True,
        available=available,
        safe_capacity=safe,
        mode=mode,
        queue_depth=len(state.waiters),
        observed_limit=state.observed_limit,
        observed_remaining=state.observed_remaining,
    )


def release(state: BucketSnapshot, demand: int) -> None:
    state.reserved = max(0, state.reserved - demand)
    state.in_flight = max(0, state.in_flight - 1)


def cancel_waiter(state: BucketSnapshot, waiter_id: str) -> None:
    state.waiters = [item for item in state.waiters if item[0] != waiter_id]


def apply_observation(
    state: BucketSnapshot, observation: CapacityObservation, now_ms: int
) -> None:
    if observation.limit is not None:
        state.observed_limit = observation.limit
    if observation.remaining is not None:
        state.observed_remaining = observation.remaining
    if observation.reset_in_seconds is not None:
        state.reset_at_ms = now_ms + int(observation.reset_in_seconds * 1000)
    state.observed_at_ms = observation.observed_at_ms or now_ms


def _observation_usable(state: BucketSnapshot, policy: CapacityPolicy, now_ms: int) -> bool:
    if state.observed_limit is None or state.observed_remaining is None:
        return False
    if state.observed_at_ms is None:
        return False
    if state.reset_at_ms is not None and state.reset_at_ms <= now_ms:
        return True
    age_s = (now_ms - state.observed_at_ms) / 1000
    return age_s <= policy.stale_observation_seconds
