"""ORCH-026 Level B — declarative dependencies integration (TC-B08).

End-to-end (DB level): B declared blocked-by A; queued B does not start until A
is 'done'; after A->done the worker can claim B. Also covers the plane/hybrid
ingestion path: Plane `blocked-by` relations are resolved to local task ids and
written into job_deps (the scheduler then reads only the DB).
"""
import os
import tempfile

import pytest

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

import src.db as db  # noqa: E402
from src.db import init_db, get_db, enqueue_job, claim_next_job  # noqa: E402
from src import task_deps  # noqa: E402


@pytest.fixture(autouse=True)
def fresh_db(tmp_path, monkeypatch):
    dbfile = tmp_path / "depsint.db"
    monkeypatch.setattr(db.settings, "db_path", str(dbfile))
    monkeypatch.setattr(db.settings, "task_deps_enabled", True, raising=False)
    init_db()
    yield


def _make_task(work_item_id, stage="development", plane_id=None):
    conn = get_db()
    pid = plane_id or work_item_id
    cur = conn.execute(
        "INSERT INTO tasks (plane_id, work_item_id, repo, branch, stage, plane_issue_id) "
        "VALUES (?, ?, ?, ?, ?, ?)",
        (pid, work_item_id, "orchestrator", f"feature/{work_item_id}", stage, pid),
    )
    tid = cur.lastrowid
    conn.commit()
    conn.close()
    return tid


def _set_stage(task_id, stage):
    conn = get_db()
    conn.execute("UPDATE tasks SET stage=? WHERE id=?", (stage, task_id))
    conn.commit()
    conn.close()


def test_b_waits_for_a_then_runs():
    a = _make_task("ORCH-200", stage="development")
    b = _make_task("ORCH-201", stage="development")
    db.add_dependency(b, a)

    job_b = enqueue_job("developer", "orchestrator", "do B", task_id=b)
    # While A is in flight, B is not claimable.
    assert claim_next_job() is None
    ready, waiting = task_deps.is_task_ready(b)
    assert ready is False and "ORCH-200" in waiting

    # A advances through to done.
    _set_stage(a, "review")
    assert claim_next_job() is None  # still not terminal
    _set_stage(a, "done")

    claimed = claim_next_job()
    assert claimed is not None and claimed["id"] == job_b


def test_multiple_predecessors_all_must_be_done():
    a1 = _make_task("ORCH-210", stage="development")
    a2 = _make_task("ORCH-211", stage="development")
    b = _make_task("ORCH-212", stage="development")
    db.add_dependency(b, a1)
    db.add_dependency(b, a2)
    job_b = enqueue_job("developer", "orchestrator", "B", task_id=b)

    _set_stage(a1, "done")
    assert claim_next_job() is None, "still blocked by a2"
    _set_stage(a2, "done")
    claimed = claim_next_job()
    assert claimed is not None and claimed["id"] == job_b


# ---- plane/hybrid ingestion path (TC-B01) ---------------------------------
def test_ingest_plane_relations_writes_db(monkeypatch):
    monkeypatch.setattr(db.settings, "task_deps_source", "hybrid", raising=False)
    a = _make_task("ORCH-220", stage="development", plane_id="plane-uuid-A")
    b = _make_task("ORCH-221", stage="development", plane_id="plane-uuid-B")

    import src.plane_sync as plane_sync
    monkeypatch.setattr(plane_sync, "fetch_blocked_by_issue_ids",
                        lambda issue_id, project_id, **k: ["plane-uuid-A"],
                        raising=False)
    n = task_deps.ingest_plane_relations(b, "plane-uuid-B", "proj-1")
    assert n == 1
    assert db.get_dependencies(b) == [a]


def test_ingest_noop_when_source_db(monkeypatch):
    monkeypatch.setattr(db.settings, "task_deps_source", "db", raising=False)
    b = _make_task("ORCH-230", stage="development", plane_id="plane-uuid-Z")
    import src.plane_sync as plane_sync
    called = {"n": 0}
    monkeypatch.setattr(plane_sync, "fetch_blocked_by_issue_ids",
                        lambda *a, **k: called.__setitem__("n", called["n"] + 1) or [],
                        raising=False)
    n = task_deps.ingest_plane_relations(b, "plane-uuid-Z", "proj-1")
    assert n == 0
    assert called["n"] == 0, "default db source must not call Plane"


def test_ingest_never_raises_on_plane_outage(monkeypatch):
    monkeypatch.setattr(db.settings, "task_deps_source", "plane", raising=False)
    b = _make_task("ORCH-240", stage="development", plane_id="plane-uuid-Y")
    import src.plane_sync as plane_sync

    def _boom(*a, **k):
        raise RuntimeError("plane down")

    monkeypatch.setattr(plane_sync, "fetch_blocked_by_issue_ids", _boom, raising=False)
    assert task_deps.ingest_plane_relations(b, "plane-uuid-Y", "proj-1") == 0
