# SPDX-License-Identifier: AGPL-3.0-only
# Copyright (c) 2026 sol pbc

from __future__ import annotations

import builtins
import json
import os

import pytest

from solstone.think import catchup_state, pipeline_health
from solstone.think.pipeline_health import (
    BACKLOG_STATE_COMPLETE,
    BACKLOG_STATE_PENDING,
    BACKLOG_STATE_UNKNOWN,
    BacklogDay,
    BacklogError,
    BacklogView,
)


@pytest.fixture
def doctor():
    from solstone.think import doctor as doctor_module

    return doctor_module


def args(doctor):
    return doctor.Args(verbose=False, json=False, jsonl=False, port=5015)


def clean_view() -> BacklogView:
    return BacklogView(
        window=30,
        days=(),
        pending_days=0,
        stuck_days=0,
        oldest_pending_day=None,
        errors=(),
    )


def unknown_day(day: str = "20200228") -> tuple[BacklogDay, BacklogError]:
    error = BacklogError(day=day, stage="terminal_states", message="boom")
    backlog_day = BacklogDay(
        day=day,
        state=BACKLOG_STATE_UNKNOWN,
        segments=0,
        units=0,
        not_sensed=0,
        why=(),
        reason=None,
        reason_code=None,
        provider=None,
        model=None,
        error=error,
    )
    return backlog_day, error


def backlog_day(day: str, state: str) -> BacklogDay:
    return BacklogDay(
        day=day,
        state=state,
        segments=0,
        units=0,
        not_sensed=0,
        why=(),
        reason=None,
        reason_code=None,
        provider=None,
        model=None,
        error=None,
    )


def test_journal_caught_up_ok_only_when_fully_clean(doctor, monkeypatch):
    monkeypatch.setattr(pipeline_health, "read_backlog_view", clean_view)

    result = doctor.journal_caught_up_check(args(doctor))

    assert result.status == "ok"
    assert result.detail == "caught up"
    assert result.severity == "advisory"


def test_journal_caught_up_unknown_day_warns_before_false_green(
    doctor,
    monkeypatch,
):
    day, error = unknown_day()
    view = BacklogView(
        window=30,
        days=(day,),
        pending_days=0,
        stuck_days=0,
        oldest_pending_day=None,
        errors=(error,),
    )
    monkeypatch.setattr(pipeline_health, "read_backlog_view", lambda: view)

    result = doctor.journal_caught_up_check(args(doctor))

    assert result.status == "warn"
    assert "couldn't fully determine" in result.detail
    assert result.status != "ok"


def test_journal_caught_up_pending_and_stuck_warn_with_distinct_counts(
    doctor,
    monkeypatch,
):
    view = BacklogView(
        window=30,
        days=(
            backlog_day("20200229", "pending"),
            backlog_day("20200301", "pending"),
            backlog_day("20200302", "stuck"),
        ),
        pending_days=2,
        stuck_days=1,
        oldest_pending_day="20200229",
        errors=(),
    )
    monkeypatch.setattr(pipeline_health, "read_backlog_view", lambda: view)

    result = doctor.journal_caught_up_check(args(doctor))

    assert result.status == "warn"
    assert result.severity == "advisory"
    assert "2 day(s) pending" in result.detail
    assert "1 day(s) stuck" in result.detail
    assert "oldest outstanding 20200229" in result.detail
    assert str(2 + 1) not in result.detail


def test_journal_caught_up_warns_for_degraded_segment_repair_day(
    doctor,
    monkeypatch,
):
    day = BacklogDay(
        day="20200229",
        state=BACKLOG_STATE_PENDING,
        segments=0,
        units=0,
        not_sensed=0,
        why=(),
        reason="segment_repair_degraded",
        reason_code="segment_repair_degraded",
        provider=None,
        model=None,
        error=None,
        segment_repair_status="degraded",
    )
    view = BacklogView(
        window=30,
        days=(day,),
        pending_days=1,
        stuck_days=0,
        oldest_pending_day="20200229",
        errors=(),
    )
    monkeypatch.setattr(pipeline_health, "read_backlog_view", lambda: view)

    result = doctor.journal_caught_up_check(args(doctor))

    assert result.status == "warn"
    assert result.status != "ok"
    assert "1 day(s) pending" in result.detail


def test_journal_caught_up_warns_for_unknown_segment_repair_day(
    doctor,
    monkeypatch,
):
    error = BacklogError(
        day="20200229",
        stage="segment_repair",
        message="segment-repair state unreadable",
    )
    day = BacklogDay(
        day="20200229",
        state=BACKLOG_STATE_UNKNOWN,
        segments=0,
        units=0,
        not_sensed=0,
        why=(),
        reason="segment_repair_unknown",
        reason_code="segment_repair_unknown",
        provider=None,
        model=None,
        error=error,
        segment_repair_status="unknown",
    )
    view = BacklogView(
        window=30,
        days=(day,),
        pending_days=0,
        stuck_days=0,
        oldest_pending_day=None,
        errors=(),
        degraded=True,
    )
    monkeypatch.setattr(pipeline_health, "read_backlog_view", lambda: view)

    result = doctor.journal_caught_up_check(args(doctor))

    assert result.status == "warn"
    assert result.status != "ok"
    assert "1 day(s) unknown" in result.detail


def test_journal_caught_up_ok_for_all_complete_view(doctor, monkeypatch):
    view = BacklogView(
        window=30,
        days=(backlog_day("20200229", BACKLOG_STATE_COMPLETE),),
        pending_days=0,
        stuck_days=0,
        oldest_pending_day=None,
        errors=(),
    )
    monkeypatch.setattr(pipeline_health, "read_backlog_view", lambda: view)

    result = doctor.journal_caught_up_check(args(doctor))

    assert result.status == "ok"
    assert result.detail == "caught up"


def test_journal_caught_up_reports_backoff_stuck_day(doctor, tmp_path, monkeypatch):
    journal = tmp_path / "journal"
    day = "20990401"
    health_dir = journal / "chronicle" / day / "health"
    health_dir.mkdir(parents=True, exist_ok=True)
    daily = health_dir / "daily.updated"
    stream = health_dir / "stream.updated"
    daily.touch()
    stream.touch()
    os.utime(daily, (100.0, 100.0))
    os.utime(stream, (200.0, 200.0))
    state_path = journal / "health" / "catchup-state.json"
    state_path.parent.mkdir(parents=True, exist_ok=True)
    state_path.write_text(
        json.dumps(
            {
                "version": catchup_state.STATE_VERSION,
                "entries": {
                    f"{day}:{catchup_state.KIND_DAILY_CATCHUP}": {
                        "day": day,
                        "command_kind": catchup_state.KIND_DAILY_CATCHUP,
                        "attempts": 3,
                        "consecutive_non_completion": 3,
                        "last_attempt_at": 1000.0,
                        "last_outcome": "interrupted",
                        "next_retry_at": 1600.0,
                        "entered_backoff_at": 1200.0,
                        "notified_at": 1200.0,
                        "fingerprint": "fingerprint",
                        "active": None,
                    }
                },
            }
        ),
        encoding="utf-8",
    )
    monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal))

    result = doctor.journal_caught_up_check(args(doctor))

    assert result.status == "warn"
    assert "0 day(s) pending" in result.detail
    assert "1 day(s) stuck" in result.detail


def test_journal_caught_up_invokes_backlog_reader_and_derives_result(
    doctor,
    monkeypatch,
):
    calls = []
    view = BacklogView(
        window=30,
        days=(
            backlog_day("20200229", "pending"),
            backlog_day("20200301", "stuck"),
        ),
        pending_days=1,
        stuck_days=1,
        oldest_pending_day="20200229",
        errors=(),
    )

    def fake_read_backlog_view():
        calls.append("read")
        return view

    monkeypatch.setattr(pipeline_health, "read_backlog_view", fake_read_backlog_view)

    result = doctor.journal_caught_up_check(args(doctor))

    assert calls == ["read"]
    assert result.status == "warn"
    assert "1 day(s) pending" in result.detail
    assert "1 day(s) stuck" in result.detail
    assert "oldest outstanding 20200229" in result.detail


def test_journal_caught_up_warns_when_backlog_read_raises(doctor, monkeypatch):
    def fake_read_backlog_view():
        raise RuntimeError("boom")

    monkeypatch.setattr(pipeline_health, "read_backlog_view", fake_read_backlog_view)

    result = doctor.journal_caught_up_check(args(doctor))

    assert result.status == "warn"
    assert result.status != "fail"
    assert result.severity == "advisory"
    assert "backlog read failed: RuntimeError: boom" in result.detail


def test_journal_caught_up_warns_when_backlog_reader_import_raises(
    doctor,
    monkeypatch,
):
    real_import = builtins.__import__

    def fail_pipeline_health_import(name, *args, **kwargs):
        if name == "solstone.think.pipeline_health":
            raise ImportError("pipeline unavailable")
        return real_import(name, *args, **kwargs)

    monkeypatch.setattr(builtins, "__import__", fail_pipeline_health_import)

    result = doctor.journal_caught_up_check(args(doctor))

    assert result.status == "warn"
    assert result.status != "fail"
    assert result.severity == "advisory"
    assert "backlog read failed: ImportError: pipeline unavailable" in result.detail


def test_journal_caught_up_warn_is_visible_but_non_blocking(doctor):
    results = [
        doctor.make_result(doctor.JOURNAL_CAUGHT_UP_CHECK, "warn", "..."),
        doctor.make_result(doctor.SERVICE_RUNNING_CHECK, "ok", "..."),
    ]

    assert doctor.jsonl_summary_status(results) == "warning"
    assert not any(
        result.severity == "blocker" and result.status == "fail" for result in results
    )


def test_journal_caught_up_is_only_in_journal_battery(doctor):
    assert "journal_caught_up" in {check.name for check, _ in doctor.JOURNAL_CHECKS}
    assert "journal_caught_up" not in {
        check.name for check, _ in doctor.UNIVERSAL_CHECKS
    }
    assert "journal_caught_up" not in {
        check.name for check, _ in doctor.READINESS_CHECKS
    }


def test_journal_caught_up_rides_existing_emission_paths(doctor, capsys):
    warn_result = doctor.make_result(doctor.JOURNAL_CAUGHT_UP_CHECK, "warn", "...")
    ok_result = doctor.make_result(doctor.JOURNAL_CAUGHT_UP_CHECK, "ok", "caught up")

    doctor.emit_json([warn_result])
    assert "journal_caught_up" in capsys.readouterr().out

    doctor.emit_jsonl(
        [warn_result],
        started_at_iso="x",
        duration_ms=0,
        summary_status="warning",
    )
    assert "journal_caught_up" in capsys.readouterr().out

    doctor.emit_text([ok_result], verbose=False)
    assert "journal_caught_up" not in capsys.readouterr().out

    doctor.emit_text([ok_result], verbose=True)
    assert "journal_caught_up" in capsys.readouterr().out
