"""Calibration configuration and tier determination for context lifecycle.

All threshold defaults are educated guesses pending empirical calibration
via Ralph loops (iterative tuning with real usage data). The from_env()
classmethod enables runtime tuning via ENGRAM_LIFECYCLE_* env vars without
code changes.

Tier determination maps ETA-to-target and used_pct to urgency levels:
  EMERGENCY: context window nearly full (immediate handoff needed)
  HANDOFF: few turns left before target (prepare handoff document)
  ADVISORY: approaching target (start collecting context)
  NONE: plenty of room (no action needed)
"""

from __future__ import annotations

import logging
import os
from dataclasses import dataclass
from enum import IntEnum

logger = logging.getLogger(__name__)


class Tier(IntEnum):
    """Urgency tier for context lifecycle actions.

    Uses IntEnum so ordering comparisons work naturally:
    EMERGENCY > HANDOFF > ADVISORY > NONE
    """

    NONE = 0
    ADVISORY = 1
    HANDOFF = 2
    EMERGENCY = 3


@dataclass(frozen=True)
class CalibrationConfig:
    """Threshold configuration for context lifecycle manager.

    All values are educated guesses pending empirical calibration.
    Use from_env() to override via ENGRAM_LIFECYCLE_* environment variables.

    Attributes:
        advisory_eta: ETA threshold for ADVISORY tier (turns remaining).
            When estimated turns to target <= this, start collecting context.
        handoff_eta: ETA threshold for HANDOFF tier (turns remaining).
            When estimated turns to target <= this, generate handoff document.
        emergency_pct: Usage percentage that triggers EMERGENCY tier.
            When used_pct >= this, immediately prepare handoff regardless of ETA.
        target_pct: Target usage percentage for velocity calculations.
            The lifecycle aims to have a handoff ready by this usage level.
        ema_alpha: Exponential moving average smoothing factor for velocity.
            Higher = more responsive to recent changes. Range: 0.0-1.0.
        min_samples: Minimum context samples before velocity estimation is valid.
            Below this, velocity is None (insufficient data).
        snooze_pct_increment: Percentage points to add when user snoozes a handoff.
            Pushes the next advisory/handoff check forward.
        max_snooze_pct: Maximum cumulative snooze percentage.
            Prevents snoozing indefinitely past a reasonable limit.
    """

    # ETA thresholds (turns remaining to target)
    advisory_eta: int = 8  # Start collecting context
    handoff_eta: int = 3  # Generate handoff document

    # Usage percentage thresholds
    emergency_pct: float = 78.0  # Immediate handoff, regardless of ETA
    target_pct: float = 75.0  # Target for velocity calculations

    # Velocity estimation parameters
    ema_alpha: float = 0.3  # EMA smoothing factor
    min_samples: int = 2  # Minimum samples for valid velocity

    # Snooze parameters
    snooze_pct_increment: float = 5.0  # Pct points added per snooze
    max_snooze_pct: float = 12.0  # Max cumulative snooze

    @classmethod
    def from_env(cls) -> CalibrationConfig:
        """Create config from ENGRAM_LIFECYCLE_* environment variables.

        Reads env vars with prefix ENGRAM_LIFECYCLE_ and maps to fields:
          ENGRAM_LIFECYCLE_ADVISORY_ETA -> advisory_eta
          ENGRAM_LIFECYCLE_HANDOFF_ETA -> handoff_eta
          ENGRAM_LIFECYCLE_EMERGENCY_PCT -> emergency_pct
          ENGRAM_LIFECYCLE_TARGET_PCT -> target_pct
          ENGRAM_LIFECYCLE_EMA_ALPHA -> ema_alpha
          ENGRAM_LIFECYCLE_MIN_SAMPLES -> min_samples
          ENGRAM_LIFECYCLE_SNOOZE_PCT_INCREMENT -> snooze_pct_increment
          ENGRAM_LIFECYCLE_MAX_SNOOZE_PCT -> max_snooze_pct

        Invalid values are silently ignored (fall back to defaults).
        """
        defaults = cls()

        def _read_int(suffix: str, default: int) -> int:
            val = os.environ.get(f"ENGRAM_LIFECYCLE_{suffix}")
            if val is None:
                return default
            try:
                return int(val)
            except (ValueError, TypeError):
                logger.warning("Invalid ENGRAM_LIFECYCLE_%s value '%s', using default", suffix, val)
                return default

        def _read_float(suffix: str, default: float) -> float:
            val = os.environ.get(f"ENGRAM_LIFECYCLE_{suffix}")
            if val is None:
                return default
            try:
                return float(val)
            except (ValueError, TypeError):
                logger.warning("Invalid ENGRAM_LIFECYCLE_%s value '%s', using default", suffix, val)
                return default

        return cls(
            advisory_eta=_read_int("ADVISORY_ETA", defaults.advisory_eta),
            handoff_eta=_read_int("HANDOFF_ETA", defaults.handoff_eta),
            emergency_pct=_read_float("EMERGENCY_PCT", defaults.emergency_pct),
            target_pct=_read_float("TARGET_PCT", defaults.target_pct),
            ema_alpha=_read_float("EMA_ALPHA", defaults.ema_alpha),
            min_samples=_read_int("MIN_SAMPLES", defaults.min_samples),
            snooze_pct_increment=_read_float("SNOOZE_PCT_INCREMENT", defaults.snooze_pct_increment),
            max_snooze_pct=_read_float("MAX_SNOOZE_PCT", defaults.max_snooze_pct),
        )


def determine_tier(
    eta_to_target: float,
    used_pct: float,
    config: CalibrationConfig,
) -> Tier:
    """Determine urgency tier based on ETA and usage percentage.

    Priority order (highest first):
      1. EMERGENCY: used_pct >= config.emergency_pct
      2. HANDOFF: eta_to_target <= config.handoff_eta
      3. ADVISORY: eta_to_target <= config.advisory_eta
      4. NONE: everything else

    Args:
        eta_to_target: Estimated turns remaining until target usage.
        used_pct: Current context window usage percentage (0-100).
        config: Calibration thresholds.

    Returns:
        The appropriate Tier for current conditions.
    """
    # Emergency takes absolute priority (percentage-based, not ETA-based)
    if used_pct >= config.emergency_pct:
        return Tier.EMERGENCY

    # ETA-based tiers (check tightest first)
    if eta_to_target <= config.handoff_eta:
        return Tier.HANDOFF

    if eta_to_target <= config.advisory_eta:
        return Tier.ADVISORY

    return Tier.NONE


def determine_tier_without_velocity(
    used_pct: float,
    config: CalibrationConfig,
) -> Tier:
    """Determine urgency tier using only usage percentage (no velocity data).

    Fallback for when velocity cannot be calculated (insufficient samples,
    duplicate turns, or daemon restart). Uses percentage-based thresholds
    derived from CalibrationConfig so intermediate tiers (ADVISORY, HANDOFF)
    are not skipped.

    Threshold derivation from config values:
      - EMERGENCY: used_pct >= config.emergency_pct (same as determine_tier)
      - HANDOFF: used_pct >= config.target_pct (at or past target = conservative)
      - ADVISORY: used_pct >= config.target_pct - 10 (approaching target)
      - NONE: everything else

    Args:
        used_pct: Current context window usage percentage (0-100).
        config: Calibration thresholds.

    Returns:
        The appropriate Tier for current conditions.
    """
    if used_pct >= config.emergency_pct:
        return Tier.EMERGENCY

    if used_pct >= config.target_pct:
        return Tier.HANDOFF

    advisory_pct = config.target_pct - 10.0
    if used_pct >= advisory_pct:
        return Tier.ADVISORY

    return Tier.NONE
