from datetime import datetime, timedelta, timezone
from pathlib import Path

import pytest

from scripts.tier2.failcount import (
 FailcountConfig,
 FailcountState,
 record_commit,
 record_green_failure,
 record_green_success,
 record_red_failure,
 should_give_up,
 from_dict,
 to_dict,
 load_state,
 save_state,
)


@pytest.fixture
def default_config() -> FailcountConfig:
 return FailcountConfig()


@pytest.fixture
def fresh_state() -> FailcountState:
 return FailcountState()


def test_initial_state_zero(fresh_state: FailcountState) -> None:
 assert fresh_state.red_phase_failures == 0
 assert fresh_state.green_phase_failures == 0
 assert fresh_state.no_progress_started_at is None


def test_red_phase_failure_increments(fresh_state: FailcountState) -> None:
 after_one = record_red_failure(fresh_state)
 assert after_one.red_phase_failures == 1
 after_two = record_red_failure(after_one)
 assert after_two.red_phase_failures == 2


def test_green_success_resets_red_counter(
 fresh_state: FailcountState, default_config: FailcountConfig
) -> None:
 after_two_red = record_red_failure(record_red_failure(fresh_state))
 assert after_two_red.red_phase_failures == 2
 now = datetime.now(timezone.utc)
 after_green = record_green_success(after_two_red, now)
 assert after_green.red_phase_failures == 0


def test_green_phase_failure_increments(fresh_state: FailcountState) -> None:
 after_one = record_green_failure(fresh_state)
 assert after_one.green_phase_failures == 1
 after_two = record_green_failure(after_one)
 assert after_two.green_phase_failures == 2


def test_no_progress_advances(
 fresh_state: FailcountState, default_config: FailcountConfig
) -> None:
 started = datetime(2026, 6, 16, 12, 0, 0, tzinfo=timezone.utc)
 after_record = record_commit(fresh_state, started)
 later = started + timedelta(minutes=31)
 assert should_give_up(after_record, default_config, later) is True


def test_no_progress_resets_on_commit(
 fresh_state: FailcountState, default_config: FailcountConfig
) -> None:
 started = datetime(2026, 6, 16, 12, 0, 0, tzinfo=timezone.utc)
 state_with_timer = record_commit(fresh_state, started)
 later = started + timedelta(minutes=25)
 state_with_reset = record_commit(state_with_timer, later)
 final = later + timedelta(minutes=25)
 assert should_give_up(state_with_reset, default_config, final) is False


def test_no_progress_resets_on_green(
 fresh_state: FailcountState, default_config: FailcountConfig
) -> None:
 started = datetime(2026, 6, 16, 12, 0, 0, tzinfo=timezone.utc)
 state_with_timer = record_commit(fresh_state, started)
 later = started + timedelta(minutes=25)
 state_with_reset = record_green_success(state_with_timer, later)
 final = later + timedelta(minutes=25)
 assert should_give_up(state_with_reset, default_config, final) is False


def test_threshold_fires_at_three(
 fresh_state: FailcountState, default_config: FailcountConfig
) -> None:
 s = record_red_failure(record_red_failure(record_red_failure(fresh_state)))
 now = datetime.now(timezone.utc)
 assert should_give_up(s, default_config, now) is True


def test_threshold_does_not_fire_at_two(
 fresh_state: FailcountState, default_config: FailcountConfig
) -> None:
 s = record_red_failure(record_red_failure(fresh_state))
 now = datetime.now(timezone.utc)
 assert should_give_up(s, default_config, now) is False


def test_multi_signal_independence(
 fresh_state: FailcountState, default_config: FailcountConfig
) -> None:
 s = record_red_failure(record_red_failure(fresh_state))
 assert s.red_phase_failures == 2
 assert s.green_phase_failures == 0
 s2 = record_green_failure(s)
 assert s2.red_phase_failures == 2
 assert s2.green_phase_failures == 1


def test_any_signal_triggers(
 fresh_state: FailcountState, default_config: FailcountConfig
) -> None:
 s = record_green_failure(record_green_failure(record_green_failure(fresh_state)))
 assert s.red_phase_failures == 0
 now = datetime.now(timezone.utc)
 assert should_give_up(s, default_config, now) is True


def test_state_persistence_round_trip() -> None:
 original = FailcountState(
  red_phase_failures=2,
  green_phase_failures=1,
  no_progress_started_at=datetime(2026, 6, 16, 12, 0, 0, tzinfo=timezone.utc),
 )
 d = to_dict(original)
 restored = from_dict(d)
 assert restored.red_phase_failures == 2
 assert restored.green_phase_failures == 1
 assert restored.no_progress_started_at == datetime(2026, 6, 16, 12, 0, 0, tzinfo=timezone.utc)


def test_configurable_thresholds() -> None:
 config = FailcountConfig(
  red_phase_threshold=5,
  green_phase_threshold=2,
  no_progress_minutes=10,
 )
 state = FailcountState(red_phase_failures=4)
 now = datetime.now(timezone.utc)
 assert should_give_up(state, config, now) is False
 state_g = FailcountState(green_phase_failures=2)
 assert should_give_up(state_g, config, now) is True


def test_load_config_reads_toml(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
 monkeypatch.syspath_prepend(str(Path(__file__).resolve().parent.parent))
 import importlib
 import scripts.tier2.failcount as fc_module
 monkeypatch.setattr(fc_module, "load_config", lambda: fc_module._load_config_from_toml(tmp_path / "fake.toml"))
 cfg = fc_module.load_config()
 assert cfg.red_phase_threshold == 3
 assert cfg.green_phase_threshold == 3
 assert cfg.no_progress_minutes == 30


def test_load_config_overrides_from_toml(tmp_path: Path) -> None:
 toml_path = tmp_path / "custom.toml"
 toml_path.write_text("red_phase_threshold = 7\ngreen_phase_threshold = 4\nno_progress_minutes = 60\n", encoding="utf-8")
 from scripts.tier2.failcount import _load_config_from_toml
 cfg = _load_config_from_toml(toml_path)
 assert cfg.red_phase_threshold == 7
 assert cfg.green_phase_threshold == 4
 assert cfg.no_progress_minutes == 60


def test_save_and_load_state_round_trip(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
 monkeypatch.setenv("TIER2_STATE_DIR", str(tmp_path))
 original = FailcountState(
  red_phase_failures=2,
  green_phase_failures=1,
  no_progress_started_at=datetime(2026, 6, 16, 12, 0, 0, tzinfo=timezone.utc),
 )
 save_state("my_track", original)
 loaded = load_state("my_track")
 assert loaded.red_phase_failures == 2
 assert loaded.green_phase_failures == 1
 assert loaded.no_progress_started_at == datetime(2026, 6, 16, 12, 0, 0, tzinfo=timezone.utc)


def test_load_state_missing_returns_fresh(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
 monkeypatch.setenv("TIER2_STATE_DIR", str(tmp_path))
 loaded = load_state("nonexistent_track")
 assert loaded.red_phase_failures == 0
 assert loaded.green_phase_failures == 0
 assert loaded.no_progress_started_at is None


def test_save_state_creates_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
 monkeypatch.setenv("TIER2_STATE_DIR", str(tmp_path))
 save_state("track", FailcountState(red_phase_failures=5))
 assert (tmp_path / "track" / "state.json").exists()


def test_load_config_integration_reads_real_toml() -> None:
 from scripts.tier2.failcount import load_config
 cfg = load_config()
 assert cfg.red_phase_threshold == 3
 assert cfg.green_phase_threshold == 3
 assert cfg.no_progress_minutes == 30
