"""ORCH-016 / TC-24 + TC-25 + AC-14: DB fallback for the duration line.

When build_status_comment is called WITHOUT an explicit duration_s but with a
task_id, it must:
  - read the last finished agent_runs row for (task_id, agent),
  - compute (julianday(finished_at) - julianday(started_at)) * 86400 in seconds,
  - format it via fmt_duration and inject the «Длительность: …» line.

Failure modes (DB locked / row missing / NULL finished_at / negative diff) must
NEVER raise; they simply suppress the duration line and let the rest of the
comment publish.
"""

import os
import tempfile

os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token")
os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token")

_test_db = os.path.join(tempfile.gettempdir(), "test_orch016_duration_fallback.db")
os.environ["ORCH_DB_PATH"] = _test_db

import pytest  # noqa: E402

from src import db as db_module  # noqa: E402
from src.db import init_db, get_db  # noqa: E402
from src import usage as U  # noqa: E402


@pytest.fixture(autouse=True)
def setup_db(monkeypatch):
    monkeypatch.setattr(db_module.settings, "db_path", _test_db, raising=False)
    if os.path.exists(_test_db):
        os.unlink(_test_db)
    init_db()
    yield
    if os.path.exists(_test_db):
        os.unlink(_test_db)


def _insert_run(task_id, agent, *, seconds_ago_start=None, finished=True):
    """Insert an agent_runs row with controllable timestamps."""
    conn = get_db()
    if seconds_ago_start is None:
        conn.execute(
            "INSERT INTO agent_runs (task_id, agent) VALUES (?, ?)",
            (task_id, agent),
        )
    else:
        if finished:
            conn.execute(
                "INSERT INTO agent_runs (task_id, agent, started_at, finished_at) "
                "VALUES (?, ?, datetime('now', ?), datetime('now'))",
                (task_id, agent, f"-{seconds_ago_start} seconds"),
            )
        else:
            conn.execute(
                "INSERT INTO agent_runs (task_id, agent, started_at) "
                "VALUES (?, ?, datetime('now', ?))",
                (task_id, agent, f"-{seconds_ago_start} seconds"),
            )
    conn.commit()
    conn.close()


# ---------------------------------------------------------------------------
# TC-24: explicit duration_s missing -> DB lookup populates the line.
# ---------------------------------------------------------------------------
def test_tc24_fallback_reads_agent_runs_for_last_finished():
    _insert_run(7, "reviewer", seconds_ago_start=240)
    secs = U.get_agent_duration(7, "reviewer")
    # SQLite's julianday math can be off by a second on either side.
    assert secs is not None and abs(secs - 240) <= 1, secs

    html = U.build_status_comment("reviewer", task_id=7)
    assert any(
        s in html for s in (
            "Длительность: 4m 00s",
            "Длительность: 4m 01s",
            "Длительность: 3m 59s",
        )
    ), html


def test_tc24_fallback_picks_last_run_when_multiple():
    _insert_run(11, "developer", seconds_ago_start=120)
    _insert_run(11, "developer", seconds_ago_start=10)
    secs = U.get_agent_duration(11, "developer")
    assert secs is not None and abs(secs - 10) <= 1, secs


def test_tc24_no_row_returns_none():
    assert U.get_agent_duration(999, "tester") is None


def test_tc24_finished_at_null_returns_none():
    _insert_run(13, "tester", seconds_ago_start=100, finished=False)
    assert U.get_agent_duration(13, "tester") is None


def test_tc24_missing_args_returns_none():
    assert U.get_agent_duration(None, "tester") is None
    assert U.get_agent_duration(7, "") is None
    assert U.get_agent_duration(0, "tester") is None


# ---------------------------------------------------------------------------
# TC-25: read failure -> logged at debug, NO exception, comment still ships.
# ---------------------------------------------------------------------------
def test_tc25_db_read_failure_no_raise(monkeypatch, caplog):
    """A locked / broken DB must not crash the status comment hot path."""
    import logging

    def _boom():
        raise RuntimeError("simulated DB outage")

    monkeypatch.setattr(U, "get_db", _boom)
    with caplog.at_level(logging.DEBUG, logger="orchestrator.usage"):
        assert U.get_agent_duration(1, "developer") is None
        # build_status_comment must still publish (no duration line, no crash).
        html = U.build_status_comment("developer", task_id=1, repo="r", branch="b")
        assert "Длительность" not in html
        assert "\U0001f4bb Developer" in html


# ---------------------------------------------------------------------------
# Sanity: explicit duration_s wins over DB fallback (no SELECT at all).
# ---------------------------------------------------------------------------
def test_explicit_duration_wins_over_db_fallback(monkeypatch):
    called = {"n": 0}
    real = U.get_agent_duration

    def _spy(task_id, agent):
        called["n"] += 1
        return real(task_id, agent)

    monkeypatch.setattr(U, "get_agent_duration", _spy)
    _insert_run(5, "architect", seconds_ago_start=300)

    html = U.build_status_comment(
        "architect", task_id=5, duration_s=12, repo="r", branch="b",
    )
    assert "Длительность: 12s" in html
    # Explicit value supplied -> DB fallback is short-circuited.
    assert called["n"] == 0
