"""Tests for calibration configuration and tier determination."""

from __future__ import annotations

import pytest

from claude_engram.calibration import (
    CalibrationConfig,
    Tier,
    determine_tier,
    determine_tier_without_velocity,
)


class TestCalibrationConfigDefaults:
    """CalibrationConfig holds all velocity thresholds with documented defaults."""

    def test_default_advisory_eta(self):
        config = CalibrationConfig()
        assert config.advisory_eta == 8

    def test_default_handoff_eta(self):
        config = CalibrationConfig()
        assert config.handoff_eta == 3

    def test_default_emergency_pct(self):
        config = CalibrationConfig()
        assert config.emergency_pct == 78.0

    def test_default_target_pct(self):
        config = CalibrationConfig()
        assert config.target_pct == 75.0

    def test_default_ema_alpha(self):
        config = CalibrationConfig()
        assert config.ema_alpha == 0.3

    def test_default_min_samples(self):
        config = CalibrationConfig()
        assert config.min_samples == 2

    def test_default_snooze_pct_increment(self):
        config = CalibrationConfig()
        assert config.snooze_pct_increment == 5.0

    def test_default_max_snooze_pct(self):
        config = CalibrationConfig()
        assert config.max_snooze_pct == 12.0


class TestCalibrationConfigOverride:
    """CalibrationConfig fields can be overridden via constructor."""

    def test_override_advisory_eta(self):
        config = CalibrationConfig(advisory_eta=10)
        assert config.advisory_eta == 10

    def test_override_handoff_eta(self):
        config = CalibrationConfig(handoff_eta=5)
        assert config.handoff_eta == 5

    def test_override_emergency_pct(self):
        config = CalibrationConfig(emergency_pct=85.0)
        assert config.emergency_pct == 85.0

    def test_override_target_pct(self):
        config = CalibrationConfig(target_pct=70.0)
        assert config.target_pct == 70.0

    def test_override_all_fields(self):
        config = CalibrationConfig(
            advisory_eta=12,
            handoff_eta=5,
            emergency_pct=85.0,
            target_pct=70.0,
            ema_alpha=0.5,
            min_samples=3,
            snooze_pct_increment=3.0,
            max_snooze_pct=10.0,
        )
        assert config.advisory_eta == 12
        assert config.handoff_eta == 5
        assert config.emergency_pct == 85.0
        assert config.target_pct == 70.0
        assert config.ema_alpha == 0.5
        assert config.min_samples == 3
        assert config.snooze_pct_increment == 3.0
        assert config.max_snooze_pct == 10.0

    def test_frozen_immutable(self):
        """CalibrationConfig is frozen (immutable)."""
        config = CalibrationConfig()
        with pytest.raises(AttributeError):
            config.advisory_eta = 99  # type: ignore[misc]


class TestCalibrationConfigFromEnv:
    """CalibrationConfig.from_env() reads ENGRAM_LIFECYCLE_* env vars."""

    def test_from_env_defaults_without_env_vars(self):
        """Without env vars, from_env() returns default config."""
        config = CalibrationConfig.from_env()
        assert config == CalibrationConfig()

    def test_from_env_reads_advisory_eta(self, monkeypatch: pytest.MonkeyPatch):
        monkeypatch.setenv("ENGRAM_LIFECYCLE_ADVISORY_ETA", "12")
        config = CalibrationConfig.from_env()
        assert config.advisory_eta == 12

    def test_from_env_reads_handoff_eta(self, monkeypatch: pytest.MonkeyPatch):
        monkeypatch.setenv("ENGRAM_LIFECYCLE_HANDOFF_ETA", "5")
        config = CalibrationConfig.from_env()
        assert config.handoff_eta == 5

    def test_from_env_reads_emergency_pct(self, monkeypatch: pytest.MonkeyPatch):
        monkeypatch.setenv("ENGRAM_LIFECYCLE_EMERGENCY_PCT", "85.0")
        config = CalibrationConfig.from_env()
        assert config.emergency_pct == 85.0

    def test_from_env_reads_target_pct(self, monkeypatch: pytest.MonkeyPatch):
        monkeypatch.setenv("ENGRAM_LIFECYCLE_TARGET_PCT", "70.0")
        config = CalibrationConfig.from_env()
        assert config.target_pct == 70.0

    def test_from_env_reads_ema_alpha(self, monkeypatch: pytest.MonkeyPatch):
        monkeypatch.setenv("ENGRAM_LIFECYCLE_EMA_ALPHA", "0.5")
        config = CalibrationConfig.from_env()
        assert config.ema_alpha == 0.5

    def test_from_env_reads_min_samples(self, monkeypatch: pytest.MonkeyPatch):
        monkeypatch.setenv("ENGRAM_LIFECYCLE_MIN_SAMPLES", "4")
        config = CalibrationConfig.from_env()
        assert config.min_samples == 4

    def test_from_env_ignores_invalid_values(self, monkeypatch: pytest.MonkeyPatch):
        """Invalid env var values fall back to defaults."""
        monkeypatch.setenv("ENGRAM_LIFECYCLE_ADVISORY_ETA", "not_a_number")
        config = CalibrationConfig.from_env()
        assert config.advisory_eta == 8  # default

    def test_from_env_partial_override(self, monkeypatch: pytest.MonkeyPatch):
        """Only specified env vars override; others keep defaults."""
        monkeypatch.setenv("ENGRAM_LIFECYCLE_ADVISORY_ETA", "15")
        config = CalibrationConfig.from_env()
        assert config.advisory_eta == 15
        assert config.handoff_eta == 3  # default preserved


class TestTierEnum:
    """Tier enum has NONE, ADVISORY, HANDOFF, EMERGENCY values."""

    def test_tier_none_exists(self):
        assert Tier.NONE is not None

    def test_tier_advisory_exists(self):
        assert Tier.ADVISORY is not None

    def test_tier_handoff_exists(self):
        assert Tier.HANDOFF is not None

    def test_tier_emergency_exists(self):
        assert Tier.EMERGENCY is not None

    def test_tier_ordering_emergency_highest(self):
        assert Tier.EMERGENCY > Tier.HANDOFF
        assert Tier.EMERGENCY > Tier.ADVISORY
        assert Tier.EMERGENCY > Tier.NONE

    def test_tier_ordering_handoff_middle(self):
        assert Tier.HANDOFF > Tier.ADVISORY
        assert Tier.HANDOFF > Tier.NONE

    def test_tier_ordering_advisory_above_none(self):
        assert Tier.ADVISORY > Tier.NONE

    def test_tier_count(self):
        """Exactly 4 tier values."""
        assert len(Tier) == 4


class TestDetermineTier:
    """determine_tier() maps ETA and usage to correct tier."""

    def test_emergency_when_usage_above_threshold(self):
        """used_pct >= 78 -> EMERGENCY regardless of ETA."""
        config = CalibrationConfig()
        assert determine_tier(eta_to_target=100, used_pct=78.0, config=config) == Tier.EMERGENCY

    def test_emergency_overrides_good_eta(self):
        """Emergency triggers even with high ETA (plenty of turns)."""
        config = CalibrationConfig()
        assert determine_tier(eta_to_target=50, used_pct=80.0, config=config) == Tier.EMERGENCY

    def test_emergency_at_exact_boundary(self):
        """Exact boundary value triggers emergency."""
        config = CalibrationConfig()
        assert determine_tier(eta_to_target=10, used_pct=78.0, config=config) == Tier.EMERGENCY

    def test_handoff_when_eta_low(self):
        """eta <= 3 -> HANDOFF (when not emergency)."""
        config = CalibrationConfig()
        assert determine_tier(eta_to_target=3, used_pct=50.0, config=config) == Tier.HANDOFF

    def test_handoff_at_exact_boundary(self):
        """eta == 3 triggers handoff."""
        config = CalibrationConfig()
        assert determine_tier(eta_to_target=3, used_pct=60.0, config=config) == Tier.HANDOFF

    def test_handoff_very_low_eta(self):
        """eta == 1 triggers handoff."""
        config = CalibrationConfig()
        assert determine_tier(eta_to_target=1, used_pct=50.0, config=config) == Tier.HANDOFF

    def test_advisory_when_eta_moderate(self):
        """eta <= 8 (but > 3) -> ADVISORY (when not emergency)."""
        config = CalibrationConfig()
        assert determine_tier(eta_to_target=8, used_pct=50.0, config=config) == Tier.ADVISORY

    def test_advisory_at_boundary(self):
        """eta == 8 triggers advisory."""
        config = CalibrationConfig()
        assert determine_tier(eta_to_target=5, used_pct=50.0, config=config) == Tier.ADVISORY

    def test_none_when_everything_fine(self):
        """High ETA, low usage -> NONE."""
        config = CalibrationConfig()
        assert determine_tier(eta_to_target=20, used_pct=30.0, config=config) == Tier.NONE

    def test_none_just_above_advisory(self):
        """eta == 9 (just above 8) -> NONE."""
        config = CalibrationConfig()
        assert determine_tier(eta_to_target=9, used_pct=50.0, config=config) == Tier.NONE

    def test_custom_config_thresholds(self):
        """Custom config changes tier boundaries."""
        config = CalibrationConfig(advisory_eta=5, handoff_eta=2, emergency_pct=90.0)
        # eta=4 is advisory with advisory_eta=5
        assert determine_tier(eta_to_target=4, used_pct=50.0, config=config) == Tier.ADVISORY
        # eta=4 is not handoff (handoff_eta=2)
        assert determine_tier(eta_to_target=4, used_pct=50.0, config=config) != Tier.HANDOFF
        # 85% is not emergency with emergency_pct=90.0
        assert determine_tier(eta_to_target=10, used_pct=85.0, config=config) == Tier.NONE

    def test_emergency_takes_priority_over_handoff(self):
        """When both emergency (high pct) and handoff (low eta), emergency wins."""
        config = CalibrationConfig()
        assert determine_tier(eta_to_target=1, used_pct=80.0, config=config) == Tier.EMERGENCY


class TestDetermineTierWithoutVelocity:
    """determine_tier_without_velocity() uses percentage-based fallback tiers."""

    def test_none_when_usage_low(self):
        """Low usage percentage -> NONE."""
        config = CalibrationConfig()
        assert determine_tier_without_velocity(used_pct=30.0, config=config) == Tier.NONE

    def test_none_just_below_advisory_threshold(self):
        """used_pct just below target_pct - 10 -> NONE."""
        config = CalibrationConfig()  # target_pct=75.0, advisory at 65.0
        assert determine_tier_without_velocity(used_pct=64.9, config=config) == Tier.NONE

    def test_advisory_at_threshold(self):
        """used_pct == target_pct - 10 -> ADVISORY."""
        config = CalibrationConfig()  # target_pct=75.0, advisory at 65.0
        assert determine_tier_without_velocity(used_pct=65.0, config=config) == Tier.ADVISORY

    def test_advisory_between_thresholds(self):
        """used_pct between advisory and handoff thresholds -> ADVISORY."""
        config = CalibrationConfig()  # advisory at 65.0, handoff at 75.0
        assert determine_tier_without_velocity(used_pct=70.0, config=config) == Tier.ADVISORY

    def test_advisory_just_below_target(self):
        """used_pct just below target_pct -> ADVISORY."""
        config = CalibrationConfig()
        assert determine_tier_without_velocity(used_pct=74.9, config=config) == Tier.ADVISORY

    def test_handoff_at_target_pct(self):
        """used_pct == target_pct -> HANDOFF."""
        config = CalibrationConfig()  # target_pct=75.0
        assert determine_tier_without_velocity(used_pct=75.0, config=config) == Tier.HANDOFF

    def test_handoff_between_target_and_emergency(self):
        """used_pct between target_pct and emergency_pct -> HANDOFF."""
        config = CalibrationConfig()  # target=75.0, emergency=78.0
        assert determine_tier_without_velocity(used_pct=76.5, config=config) == Tier.HANDOFF

    def test_handoff_just_below_emergency(self):
        """used_pct just below emergency_pct -> HANDOFF."""
        config = CalibrationConfig()
        assert determine_tier_without_velocity(used_pct=77.9, config=config) == Tier.HANDOFF

    def test_emergency_at_threshold(self):
        """used_pct == emergency_pct -> EMERGENCY."""
        config = CalibrationConfig()  # emergency_pct=78.0
        assert determine_tier_without_velocity(used_pct=78.0, config=config) == Tier.EMERGENCY

    def test_emergency_above_threshold(self):
        """used_pct well above emergency_pct -> EMERGENCY."""
        config = CalibrationConfig()
        assert determine_tier_without_velocity(used_pct=90.0, config=config) == Tier.EMERGENCY

    def test_custom_config_shifts_thresholds(self):
        """Custom config shifts all percentage thresholds accordingly."""
        config = CalibrationConfig(target_pct=80.0, emergency_pct=90.0)
        # advisory at 80 - 10 = 70
        assert determine_tier_without_velocity(used_pct=69.9, config=config) == Tier.NONE
        assert determine_tier_without_velocity(used_pct=70.0, config=config) == Tier.ADVISORY
        assert determine_tier_without_velocity(used_pct=79.9, config=config) == Tier.ADVISORY
        assert determine_tier_without_velocity(used_pct=80.0, config=config) == Tier.HANDOFF
        assert determine_tier_without_velocity(used_pct=89.9, config=config) == Tier.HANDOFF
        assert determine_tier_without_velocity(used_pct=90.0, config=config) == Tier.EMERGENCY

    def test_zero_usage(self):
        """0% usage -> NONE."""
        config = CalibrationConfig()
        assert determine_tier_without_velocity(used_pct=0.0, config=config) == Tier.NONE

    def test_full_usage(self):
        """100% usage -> EMERGENCY."""
        config = CalibrationConfig()
        assert determine_tier_without_velocity(used_pct=100.0, config=config) == Tier.EMERGENCY
