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

import argparse
import os

import pytest


@pytest.fixture
def journal_path(tmp_path, monkeypatch):
    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    (tmp_path / "health").mkdir()
    return tmp_path


@pytest.fixture
def heartbeat_mocks(monkeypatch):
    state = {"run_calls": [], "append_calls": []}

    def fake_run_recipe_pass(today):
        state["run_calls"].append(today)
        return {"fired": [], "escalated_targets": [], "data_source_errors": []}

    def fake_append_steward_event(event, **fields):
        state["append_calls"].append((event, fields))

    monkeypatch.setattr(
        "solstone.think.heartbeat.setup_cli",
        lambda parser: argparse.Namespace(force=False),
    )
    monkeypatch.setattr(
        "solstone.think.heartbeat.run_recipe_pass",
        fake_run_recipe_pass,
    )
    monkeypatch.setattr(
        "solstone.think.heartbeat.append_steward_event",
        fake_append_steward_event,
    )
    return state


def test_heartbeat_command_mapping():
    """heartbeat key in COMMANDS maps to solstone.think.heartbeat module."""
    from solstone.think.sol_cli import COMMANDS

    assert COMMANDS["heartbeat"].module == "solstone.think.heartbeat"


def test_heartbeat_main_is_callable():
    """solstone.think.heartbeat.main is a callable function."""
    from solstone.think.heartbeat import main

    assert callable(main)


def test_pid_guard_live_process_exits_zero(journal_path, heartbeat_mocks):
    """When PID file contains current process PID, main() exits 0 without a pass."""
    import solstone.think.heartbeat as mod

    pid_file = journal_path / "health" / "heartbeat.pid"
    pid_file.write_text(str(os.getpid()))

    with pytest.raises(SystemExit) as exc_info:
        mod.main()
    assert exc_info.value.code == 0
    assert heartbeat_mocks["run_calls"] == []
    assert heartbeat_mocks["append_calls"] == []


def test_pid_guard_dead_process_removes_stale_pid(journal_path, heartbeat_mocks):
    """When PID file contains a dead PID, main() removes it and runs the pass."""
    import solstone.think.heartbeat as mod

    pid_file = journal_path / "health" / "heartbeat.pid"
    dead_pid = 99999999
    try:
        os.kill(dead_pid, 0)
        pytest.skip("PID 99999999 is unexpectedly alive")
    except ProcessLookupError:
        pass

    pid_file.write_text(str(dead_pid))

    with pytest.raises(SystemExit) as exc_info:
        mod.main()
    assert exc_info.value.code == 0
    assert len(heartbeat_mocks["run_calls"]) == 1
    assert not pid_file.exists()


def test_pid_file_created_and_removed_on_success(
    journal_path, heartbeat_mocks, monkeypatch
):
    """PID file exists during execution and is removed after main() completes."""
    import solstone.think.heartbeat as mod

    pid_file = journal_path / "health" / "heartbeat.pid"
    pid_during_run = []

    def capture_pid_run(today):
        pid_during_run.append(pid_file.exists())
        if pid_file.exists():
            pid_during_run.append(pid_file.read_text().strip())
        return {"fired": [], "escalated_targets": [], "data_source_errors": []}

    monkeypatch.setattr(mod, "run_recipe_pass", capture_pid_run)

    with pytest.raises(SystemExit):
        mod.main()

    assert pid_during_run[0] is True
    assert pid_during_run[1] == str(os.getpid())
    assert not pid_file.exists()


def test_pid_file_removed_on_error(journal_path, heartbeat_mocks, monkeypatch):
    """PID file is removed when the deterministic pass raises."""
    import solstone.think.heartbeat as mod

    pid_file = journal_path / "health" / "heartbeat.pid"

    def fail_run(today):
        raise RuntimeError("boom")

    monkeypatch.setattr(mod, "run_recipe_pass", fail_run)

    with pytest.raises(SystemExit) as exc_info:
        mod.main()
    assert exc_info.value.code == 1
    assert not pid_file.exists()
    content = (journal_path / "health" / "heartbeat.log").read_text()
    assert "outcome=error" in content


def test_log_run_appends_line(journal_path):
    """_log_run appends a correctly formatted line to heartbeat.log."""
    import time

    from solstone.think.heartbeat import _log_run

    health_dir = journal_path / "health"
    start_time = time.monotonic() - 5

    _log_run(health_dir, start_time, "success")

    log_file = health_dir / "heartbeat.log"
    assert log_file.exists()
    content = log_file.read_text()
    assert content.endswith("\n")
    line = content.strip()
    assert "duration=" in line
    assert "outcome=success" in line


def test_log_written_after_successful_run(journal_path, heartbeat_mocks):
    """After a successful main() run, heartbeat.log has a success entry."""
    import solstone.think.heartbeat as mod

    with pytest.raises(SystemExit) as exc_info:
        mod.main()
    assert exc_info.value.code == 0

    log_file = journal_path / "health" / "heartbeat.log"
    assert log_file.exists()
    content = log_file.read_text()
    assert "outcome=success" in content


def test_pass_runs_once_and_no_cortex_path(journal_path, heartbeat_mocks):
    """A normal run invokes one deterministic pass and has no cortex symbols."""
    import solstone.think.heartbeat as mod

    with pytest.raises(SystemExit) as exc_info:
        mod.main()
    assert exc_info.value.code == 0

    assert len(heartbeat_mocks["run_calls"]) == 1
    assert not hasattr(mod, "cortex_request")
    assert not hasattr(mod, "wait_for_uses")


def test_pass_event_persisted(journal_path, heartbeat_mocks, monkeypatch):
    """A successful run records the deterministic pass result."""
    import solstone.think.heartbeat as mod

    monkeypatch.setattr(
        mod,
        "run_recipe_pass",
        lambda today: {
            "fired": [],
            "escalated_targets": [],
            "data_source_errors": [],
        },
    )

    with pytest.raises(SystemExit) as exc_info:
        mod.main()
    assert exc_info.value.code == 0

    assert heartbeat_mocks["append_calls"] == [
        (
            "pass",
            {
                "fired": [],
                "escalated_targets": [],
                "data_source_errors": [],
            },
        )
    ]


def test_recency_check_skips_recent_heartbeat(journal_path, heartbeat_mocks):
    """When heartbeat.log has a recent success, main() exits 0 without a pass."""
    from datetime import datetime

    import solstone.think.heartbeat as mod

    # Write a recent success entry
    log_file = journal_path / "health" / "heartbeat.log"
    recent_ts = datetime.now().isoformat(timespec="seconds")
    log_file.write_text(f"{recent_ts} duration=5s outcome=success\n")

    with pytest.raises(SystemExit) as exc_info:
        mod.main()
    assert exc_info.value.code == 0
    assert heartbeat_mocks["run_calls"] == []
    assert heartbeat_mocks["append_calls"] == []


def test_recency_check_runs_after_old_heartbeat(journal_path, heartbeat_mocks):
    """When heartbeat.log success is older than the window, main() runs the pass."""
    from datetime import datetime, timedelta

    import solstone.think.heartbeat as mod

    # Write an old success entry (24 hours ago)
    log_file = journal_path / "health" / "heartbeat.log"
    old_ts = (datetime.now() - timedelta(hours=24)).isoformat(timespec="seconds")
    log_file.write_text(f"{old_ts} duration=5s outcome=success\n")

    with pytest.raises(SystemExit):
        mod.main()
    assert len(heartbeat_mocks["run_calls"]) == 1


def test_force_flag_bypasses_recency_check(journal_path, heartbeat_mocks, monkeypatch):
    """--force runs full check even with a recent success."""
    import solstone.think.heartbeat as mod

    monkeypatch.setattr(
        "solstone.think.heartbeat.setup_cli",
        lambda parser: argparse.Namespace(force=True),
    )

    # Write a recent success entry
    from datetime import datetime

    log_file = journal_path / "health" / "heartbeat.log"
    recent_ts = datetime.now().isoformat(timespec="seconds")
    log_file.write_text(f"{recent_ts} duration=5s outcome=success\n")

    with pytest.raises(SystemExit):
        mod.main()
    assert len(heartbeat_mocks["run_calls"]) == 1


def test_last_success_time_parses_log(journal_path):
    """_last_success_time returns the timestamp of the most recent success."""
    from solstone.think.heartbeat import _last_success_time

    health_dir = journal_path / "health"
    log_file = health_dir / "heartbeat.log"
    log_file.write_text(
        "2026-03-19T08:00:00 duration=120s outcome=success\n"
        "2026-03-19T12:00:00 duration=5s outcome=error\n"
        "2026-03-19T14:00:00 duration=90s outcome=success\n"
    )

    result = _last_success_time(health_dir)
    assert result is not None
    assert result.hour == 14
    assert result.day == 19


def test_last_success_time_returns_none_for_no_log(journal_path):
    """_last_success_time returns None when no log file exists."""
    from solstone.think.heartbeat import _last_success_time

    result = _last_success_time(journal_path / "health")
    assert result is None


def test_last_success_time_returns_none_for_no_successes(journal_path):
    """_last_success_time returns None when log has no success entries."""
    from solstone.think.heartbeat import _last_success_time

    health_dir = journal_path / "health"
    log_file = health_dir / "heartbeat.log"
    log_file.write_text(
        "2026-03-19T08:00:00 duration=5s outcome=error\n"
        "2026-03-19T12:00:00 duration=5s outcome=error\n"
    )

    result = _last_success_time(health_dir)
    assert result is None


def test_think_emit_daily_complete_shape(monkeypatch):
    """think.emit('daily_complete', ...) calls _callosum.emit with correct tract and fields."""
    from unittest.mock import Mock

    import solstone.think.thinking as think_mod

    mock_conn = Mock()
    monkeypatch.setattr(think_mod, "_callosum", mock_conn)

    think_mod.emit(
        "daily_complete", day="20260318", success=3, failed=0, duration_ms=5000
    )

    mock_conn.emit.assert_called_once_with(
        "think",
        "daily_complete",
        day="20260318",
        success=3,
        failed=0,
        duration_ms=5000,
    )


def test_think_emit_noop_without_callosum(monkeypatch):
    """think.emit() does nothing when _callosum is None."""
    import solstone.think.thinking as think_mod

    monkeypatch.setattr(think_mod, "_callosum", None)
    think_mod.emit("daily_complete", day="20260318")
