"""ORCH-053 (F-3): sha->branch resolution hardening in handle_ci_status.

When a CI-status webhook carries no ``branches[]`` and the SHA cannot be
resolved to a feature branch via ``git branch -r --contains`` (lost on a 502
rebuild, shallow clone, etc.), handle_ci_status now falls back to the tasks DB
and matches the UNIQUE development-stage task of the repo. Ambiguity (more than
one development task) is deliberately left unresolved so it can never make a
false match.

The git subprocess and the network QG / Plane / Telegram side effects are mocked
so the handler runs offline against a real isolated sqlite DB.
"""

import asyncio
import os
import tempfile
from types import SimpleNamespace

import pytest

_test_db = os.path.join(tempfile.gettempdir(), "test_orchestrator_gitea_sha.db")
os.environ["ORCH_DB_PATH"] = _test_db
os.environ["ORCH_REPOS_DIR"] = tempfile.gettempdir()
os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token")
os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token")

from unittest.mock import MagicMock  # noqa: E402

import src.db as _db  # noqa: E402
from src.db import init_db, get_db  # noqa: E402
from src.webhooks import gitea as gitea_mod  # noqa: E402


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


@pytest.fixture(autouse=True)
def silence_and_stub_git(monkeypatch):
    # git branch -r --contains <sha> resolves to nothing (forces the DB fallback).
    monkeypatch.setattr(
        gitea_mod.subprocess, "run",
        lambda *a, **k: SimpleNamespace(stdout="", returncode=0),
    )
    # Mute the network side effects bound module-level in gitea.
    for name in ("notify_stage_change", "notify_qg_failure", "notify_error",
                 "plane_notify_stage"):
        monkeypatch.setattr(gitea_mod, name, MagicMock(), raising=False)


def _make_dev_task(branch, wi, repo="enduro-trails"):
    conn = get_db()
    cur = conn.execute(
        "INSERT INTO tasks (plane_id, work_item_id, repo, branch, stage) "
        "VALUES (?, ?, ?, ?, 'development')",
        (f"plane-{wi}", wi, repo, branch),
    )
    tid = cur.lastrowid
    conn.commit()
    conn.close()
    return tid


def _stage_of(task_id):
    conn = get_db()
    row = conn.execute("SELECT stage FROM tasks WHERE id = ?", (task_id,)).fetchone()
    conn.close()
    return row["stage"]


def _ci_payload(sha="deadbeef", repo="enduro-trails", state="success"):
    return {
        "state": state,
        "sha": sha,
        "branches": [],  # no branch in the event -> forces resolution
        "repository": {"name": repo},
    }


# ---------------------------------------------------------------------------
# TC-18: unique development task -> DB fallback resolves the branch, advances.
# ---------------------------------------------------------------------------
def test_tc18_db_fallback_unique_match_advances(monkeypatch):
    ci = MagicMock(return_value=(True, "CI green"))
    monkeypatch.setattr(gitea_mod, "check_ci_green", ci)

    tid = _make_dev_task("feature/ET-050-x", "ET-050")

    asyncio.run(gitea_mod.handle_ci_status(_ci_payload()))

    assert _stage_of(tid) == "review"
    ci.assert_called_once()
    # The fallback resolved to the unique dev task's branch.
    assert ci.call_args.args[1] == "feature/ET-050-x"


# ---------------------------------------------------------------------------
# TC-19: several development tasks -> ambiguous -> no false match, no advance.
# ---------------------------------------------------------------------------
def test_tc19_db_fallback_ambiguous_no_match(monkeypatch, caplog):
    ci = MagicMock(return_value=(True, "CI green"))
    monkeypatch.setattr(gitea_mod, "check_ci_green", ci)

    t1 = _make_dev_task("feature/ET-051-a", "ET-051")
    t2 = _make_dev_task("feature/ET-052-b", "ET-052")

    with caplog.at_level("INFO", logger="orchestrator.webhooks.gitea"):
        asyncio.run(gitea_mod.handle_ci_status(_ci_payload()))

    # Ambiguity -> branch unresolved -> handler returns before touching the gate.
    assert _stage_of(t1) == "development"
    assert _stage_of(t2) == "development"
    ci.assert_not_called()
    assert "could not determine branch" in caplog.text
