"""ORCH-042: bump-mode live tracker + delete_telegram helper.

bump = delete(old) + send(new, silent) + repoint message_id. One card per task,
always at the bottom. Covers AC-7..AC-14 (TC-07..TC-17). The edit-mode regression
stays in tests/test_telegram_tracker.py.

Isolated temp DB; no network (httpx / low-level helpers are patched per case).
"""

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_orchestrator_tracker_bump.db")
os.environ["ORCH_DB_PATH"] = _test_db

from unittest.mock import MagicMock, patch  # noqa: E402

import pytest  # noqa: E402

import src.db as db_module  # noqa: E402
from src.db import (  # noqa: E402
    init_db, get_db, get_tracker_message_id, set_tracker_message_id,
)
from src import notifications as N  # 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 _mk_task(stage="development", wid="ORCH-042"):
    conn = get_db()
    cur = conn.execute(
        "INSERT INTO tasks (plane_id, work_item_id, repo, branch, stage, title) "
        "VALUES (?, ?, ?, ?, ?, ?)",
        ("p1", wid, "orchestrator", "feature/ORCH-042-x", stage, "bump test"),
    )
    tid = cur.lastrowid
    conn.commit()
    conn.close()
    return tid


def _bump_mode(monkeypatch):
    monkeypatch.setattr(N._get_settings(), "tracker_mode", "bump", raising=False)


# --------------------------------------------------------------------------- #
# bump mode behaviour
# --------------------------------------------------------------------------- #
def test_bump_first_call_sends_silent_no_delete(monkeypatch):
    """TC-07/AC-7,AC-9: first call (no id) -> NO delete, silent send, id stored."""
    _bump_mode(monkeypatch)
    tid = _mk_task(stage="analysis")

    sends = []
    monkeypatch.setattr(N, "send_telegram",
                        lambda text, disable_notification=False:
                        sends.append(disable_notification) or 555)
    monkeypatch.setattr(N, "delete_telegram",
                        lambda mid: (_ for _ in ()).throw(
                            AssertionError("delete must not run on first call")))

    N.update_task_tracker(tid)

    assert sends == [True]  # exactly one silent send
    assert get_tracker_message_id(tid) == 555


def test_bump_repeat_deletes_then_sends_and_repoints(monkeypatch):
    """TC-08/AC-8,AC-9,AC-10: repeat -> delete(old) THEN send(silent), id repointed."""
    _bump_mode(monkeypatch)
    tid = _mk_task()
    set_tracker_message_id(tid, 100)

    order = []
    monkeypatch.setattr(N, "delete_telegram",
                        lambda mid: order.append(("delete", mid)) or True)
    monkeypatch.setattr(N, "send_telegram",
                        lambda text, disable_notification=False:
                        order.append(("send", disable_notification)) or 200)

    N.update_task_tracker(tid)

    assert order == [("delete", 100), ("send", True)]  # delete before send, silent
    assert get_tracker_message_id(tid) == 200  # repointed to the new card


def test_bump_delete_fail_still_sends(monkeypatch):
    """TC-09/AC-11: delete_telegram->False -> new card still sent, id updated."""
    _bump_mode(monkeypatch)
    tid = _mk_task()
    set_tracker_message_id(tid, 100)

    sends = []
    monkeypatch.setattr(N, "delete_telegram", lambda mid: False)  # >48h / transient
    monkeypatch.setattr(N, "send_telegram",
                        lambda text, disable_notification=False:
                        sends.append(disable_notification) or 201)

    N.update_task_tracker(tid)

    assert sends == [True]  # exactly one send despite delete failing
    assert get_tracker_message_id(tid) == 201


def test_bump_send_none_keeps_old_id(monkeypatch):
    """TC-10/AC-13: send->None (transient) -> id NOT wiped, one send attempt."""
    _bump_mode(monkeypatch)
    tid = _mk_task()
    set_tracker_message_id(tid, 100)

    sends = []
    monkeypatch.setattr(N, "delete_telegram", lambda mid: True)
    monkeypatch.setattr(N, "send_telegram",
                        lambda text, disable_notification=False:
                        sends.append(1) or None)

    N.update_task_tracker(tid)  # must not raise

    assert len(sends) == 1  # exactly one (failed) attempt, no retry/dupe
    assert get_tracker_message_id(tid) == 100  # pointer preserved, not None


def test_bump_one_card_per_call(monkeypatch):
    """TC-11/AC-10: at most one send_telegram per update_task_tracker call."""
    _bump_mode(monkeypatch)
    tid = _mk_task()
    set_tracker_message_id(tid, 100)

    sends = []
    monkeypatch.setattr(N, "delete_telegram", lambda mid: True)
    monkeypatch.setattr(N, "send_telegram",
                        lambda text, disable_notification=False:
                        sends.append(1) or 202)

    N.update_task_tracker(tid)
    assert len(sends) == 1


# --------------------------------------------------------------------------- #
# delete_telegram classification (httpx mocked, never raises)
# --------------------------------------------------------------------------- #
def _del_resp(ok, description=None):
    resp = MagicMock()
    body = {"ok": ok}
    if description is not None:
        body["description"] = description
    resp.json.return_value = body
    return resp


def _patch_tg_creds(monkeypatch):
    monkeypatch.setattr(N._get_settings(), "telegram_bot_token", "T", raising=False)
    monkeypatch.setattr(N._get_settings(), "telegram_chat_id", "C", raising=False)


def test_delete_ok_true(monkeypatch):
    """TC-12: ok:true -> True."""
    _patch_tg_creds(monkeypatch)
    with patch("src.notifications.httpx") as hx:
        hx.post.return_value = _del_resp(True)
        assert N.delete_telegram(1) is True


@pytest.mark.parametrize("desc", [
    "Bad Request: message to delete not found",
    "Bad Request: message can't be deleted",
    "Bad Request: MESSAGE_ID_INVALID",
])
def test_delete_gone_markers_are_true(monkeypatch, desc):
    """TC-13/AC-12: 'already gone / can't delete' -> True (not transient)."""
    _patch_tg_creds(monkeypatch)
    with patch("src.notifications.httpx") as hx:
        hx.post.return_value = _del_resp(False, desc)
        assert N.delete_telegram(1) is True


@pytest.mark.parametrize("desc", [
    "Bad Request: some other unexpected error",
    "Internal Server Error",
])
def test_delete_unknown_or_5xx_is_false(monkeypatch, desc):
    """TC-14/AC-12: unknown ok:false / 5xx -> False (old may still be alive)."""
    _patch_tg_creds(monkeypatch)
    with patch("src.notifications.httpx") as hx:
        hx.post.return_value = _del_resp(False, desc)
        assert N.delete_telegram(1) is False


def test_delete_exception_is_false(monkeypatch):
    """TC-15/AC-12,AC-14: timeout/network -> False, never raises."""
    _patch_tg_creds(monkeypatch)
    with patch("src.notifications.httpx") as hx:
        hx.post.side_effect = Exception("read timeout")
        assert N.delete_telegram(1) is False


def test_delete_no_creds_is_false_and_no_http(monkeypatch):
    """TC-16/AC-12: no token/chat_id -> False, HTTP not called."""
    monkeypatch.setattr(N._get_settings(), "telegram_bot_token", "", raising=False)
    monkeypatch.setattr(N._get_settings(), "telegram_chat_id", "", raising=False)
    with patch("src.notifications.httpx") as hx:
        assert N.delete_telegram(1) is False
        hx.post.assert_not_called()


# --------------------------------------------------------------------------- #
# never raises in either mode
# --------------------------------------------------------------------------- #
def test_update_task_tracker_never_raises_bump(monkeypatch):
    """TC-17/AC-14: bump path swallows a render/DB explosion."""
    _bump_mode(monkeypatch)
    tid = _mk_task()
    monkeypatch.setattr(N, "render_task_tracker",
                        lambda task_id: (_ for _ in ()).throw(RuntimeError("boom")))
    # Must not raise.
    N.update_task_tracker(tid)


def test_update_task_tracker_never_raises_edit(monkeypatch):
    """TC-17/AC-14: edit path swallows a render/DB explosion."""
    monkeypatch.setattr(N._get_settings(), "tracker_mode", "edit", raising=False)
    tid = _mk_task()
    monkeypatch.setattr(N, "render_task_tracker",
                        lambda task_id: (_ for _ in ()).throw(RuntimeError("boom")))
    N.update_task_tracker(tid)
