"""Failure-counting module for Tier 2 autonomous runs.

Tracks three orthogonal signals: red-phase failures, green-phase failures,
and no-progress wall-clock. When any signal hits its threshold, the run
gives up and writes a failure report.

Pure logic, no external dependencies. Fully unit-testable.
"""
from __future__ import annotations

import os
from dataclasses import asdict, dataclass
from datetime import datetime
from pathlib import Path
from typing import Any

import tomllib


@dataclass
class FailcountConfig:
 """Threshold configuration. Loaded from failcount.toml or defaults."""
 red_phase_threshold: int = 3
 green_phase_threshold: int = 3
 no_progress_minutes: int = 30


@dataclass
class FailcountState:
 """The three failure signals + the no-progress timer anchor."""
 red_phase_failures: int = 0
 green_phase_failures: int = 0
 no_progress_started_at: datetime | None = None


def _load_config_from_toml(path: Path) -> FailcountConfig:
 if not path.exists():
  return FailcountConfig()
 with path.open("rb") as f:
  data = tomllib.load(f)
 return FailcountConfig(
  red_phase_threshold=data.get("red_phase_threshold", 3),
  green_phase_threshold=data.get("green_phase_threshold", 3),
  no_progress_minutes=data.get("no_progress_minutes", 30),
 )


def load_config() -> FailcountConfig:
 """Load config from `scripts/tier2/failcount.toml`, or defaults."""
 toml_path = Path(__file__).parent / "failcount.toml"
 return _load_config_from_toml(toml_path)


def record_red_failure(state: FailcountState) -> FailcountState:
 return FailcountState(
  red_phase_failures=state.red_phase_failures + 1,
  green_phase_failures=state.green_phase_failures,
  no_progress_started_at=state.no_progress_started_at,
 )


def record_green_failure(state: FailcountState) -> FailcountState:
 return FailcountState(
  red_phase_failures=state.red_phase_failures,
  green_phase_failures=state.green_phase_failures + 1,
  no_progress_started_at=state.no_progress_started_at,
 )


def record_green_success(state: FailcountState, now: datetime) -> FailcountState:
 return FailcountState(
  red_phase_failures=0,
  green_phase_failures=0,
  no_progress_started_at=now,
 )


def record_commit(state: FailcountState, now: datetime) -> FailcountState:
 return FailcountState(
  red_phase_failures=state.red_phase_failures,
  green_phase_failures=state.green_phase_failures,
  no_progress_started_at=now,
 )


def should_give_up(state: FailcountState, config: FailcountConfig, now: datetime) -> bool:
 if state.red_phase_failures >= config.red_phase_threshold:
  return True
 if state.green_phase_failures >= config.green_phase_threshold:
  return True
 if state.no_progress_started_at is not None:
  elapsed_minutes = (now - state.no_progress_started_at).total_seconds() / 60.0
  if elapsed_minutes >= config.no_progress_minutes:
   return True
 return False


def to_dict(state: FailcountState) -> dict[str, Any]:
 d = asdict(state)
 if state.no_progress_started_at is not None:
  d["no_progress_started_at"] = state.no_progress_started_at.isoformat()
 return d


def from_dict(d: dict[str, Any]) -> FailcountState:
 no_progress = d.get("no_progress_started_at")
 if isinstance(no_progress, str):
  no_progress = datetime.fromisoformat(no_progress)
 return FailcountState(
  red_phase_failures=d.get("red_phase_failures", 0),
  green_phase_failures=d.get("green_phase_failures", 0),
  no_progress_started_at=no_progress,
 )


def _state_dir(track_name: str) -> Path:
 env_base = os.environ.get("TIER2_STATE_DIR")
 if env_base:
  return Path(env_base) / track_name
 project_root = Path(__file__).resolve().parents[2]
 return project_root / "tests" / "artifacts" / "tier2_state" / track_name


def load_state(track_name: str) -> FailcountState:
 path = _state_dir(track_name) / "state.json"
 if not path.exists():
  return FailcountState()
 import json
 with path.open("r", encoding="utf-8") as f:
  return from_dict(json.load(f))


def save_state(track_name: str, state: FailcountState) -> None:
 import json
 d = _state_dir(track_name)
 d.mkdir(parents=True, exist_ok=True)
 target = d / "state.json"
 tmp = d / "state.json.tmp"
 with tmp.open("w", encoding="utf-8") as f:
  json.dump(to_dict(state), f, indent=2)
 os.replace(tmp, target)
