"""Проверка контроля зависших задач без живого исполнителя (R01, R02, R03).

Рычаги порчи:
1. Не проверять зависание задач в gates.py — покраснеет `test_stale_task_triggers_problem_in_why`.
2. Не сбрасывать таймаут при паузе — покраснеет `test_paused_run_does_not_flag_stale_task`.
"""

from __future__ import annotations

import datetime as dt
import os
import subprocess
import sys
from pathlib import Path

import pytest

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))

from chekan import gates, proof, store, why  # noqa: E402
from chekan.__main__ import main  # noqa: E402
from chekan.store import Run  # noqa: E402


@pytest.fixture
def repo(tmp_path: Path) -> Path:
    subprocess.run(["git", "init", "-q"], cwd=tmp_path, check=True)
    subprocess.run(["git", "config", "user.email", "t@t"], cwd=tmp_path, check=True)
    subprocess.run(["git", "config", "user.name", "t"], cwd=tmp_path, check=True)
    (tmp_path / "app.py").write_text("print('ok')\n", encoding="utf-8")
    subprocess.run(["git", "add", "-A"], cwd=tmp_path, check=True)
    subprocess.run(["git", "commit", "-qm", "init"], cwd=tmp_path, check=True)
    r = Run.create(tmp_path, "Тестовый прогон", "normal", "T2")
    r.add_requirement("R01", "цитата", "причина")
    r.add_task("01", "Первая задача", ["R01"], zone="app.py", verify="true")
    r.set_task_status("01", "in-progress")
    return tmp_path


def _shift_clock(monkeypatch, minutes: int) -> None:
    real = dt.datetime.now

    def later(tz=None):
        return real(tz) + dt.timedelta(minutes=minutes)

    func = lambda: later(dt.timezone.utc).astimezone().isoformat(timespec="seconds")
    monkeypatch.setattr(store, "now", func)
    monkeypatch.setattr(proof, "now", func)


def test_active_task_is_not_stale(repo: Path):
    run = Run.load(repo)
    assert gates.stale_tasks(run) == []
    problems = gates.build_invariant(run)
    assert not any("затих без движения" in p for p in problems)


def test_stale_task_triggers_problem_in_why(repo: Path, monkeypatch):
    """Спустя 25 минут без движения задача краснеет и появляется в chekan why."""
    _shift_clock(monkeypatch, 25)
    run = Run.load(repo)
    stale = gates.stale_tasks(run)
    assert len(stale) == 1
    assert "таск 01 затих без движения более 25 мин" in stale[0]
    assert "chekan task reset 01" in stale[0]

    claims = why.claims(run)
    stale_claims = [c for c in claims if "затих без движения" in c.problem]
    assert len(stale_claims) == 1
    assert stale_claims[0].fix == "chekan task reset 01"
    assert stale_claims[0].who == why.ORCHESTRATOR


def test_paused_run_does_not_flag_stale_task(repo: Path, monkeypatch):
    """На паузе тишина законна и не делает задачу зависшей."""
    run = Run.load(repo)
    run.pause()
    _shift_clock(monkeypatch, 60)
    after = Run.load(repo)
    assert gates.stale_tasks(after) == []


def test_evidence_activity_resets_stale_timer(repo: Path, monkeypatch):
    """Запись в журнале доказательств обновляет активность задачи."""
    run = Run.load(repo)
    _shift_clock(monkeypatch, 15)
    proof.run_command(run, "true", task="01")
    _shift_clock(monkeypatch, 25)  # 10 мин после команды
    after = Run.load(repo)
    assert gates.stale_tasks(after) == []


def test_custom_timeout_via_env(repo: Path, monkeypatch):
    """CHEKAN_TASK_STALE_TIMEOUT_SEC позволяет переопределить порог."""
    monkeypatch.setenv("CHEKAN_TASK_STALE_TIMEOUT_SEC", "300")  # 5 минут
    run = Run.load(repo)
    _shift_clock(monkeypatch, 6)
    stale = gates.stale_tasks(run)
    assert len(stale) == 1
    assert "таск 01 затих без движения" in stale[0]


def test_task_reset_clears_stale_blocker(repo: Path, monkeypatch):
    """Сброс задачи возвращает её в pending и снимает блокировку."""
    _shift_clock(monkeypatch, 30)
    run = Run.load(repo)
    assert len(gates.stale_tasks(run)) == 1

    cwd = Path.cwd()
    try:
        os.chdir(repo)
        main(["task", "reset", "01"])
    finally:
        os.chdir(cwd)

    after = Run.load(repo)
    assert after.task("01")["status"] == "pending"
    assert gates.stale_tasks(after) == []
