"""Tests for worker spawn, status polling, fallback retry, and re-entrancy."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from vibepod.core.orchestrate.worker import (
    read_status_file,
    StatusResult,
    tasks_ready_to_run,
)
from vibepod.core.orchestrate.plan_file import OrchestrateTask, OrchestratePlan


def _make_task(
    id: str,
    status: str = "pending",
    depends_on: list[str] | None = None,
    fallback: str | None = "claude",
) -> OrchestrateTask:
    return OrchestrateTask(
        id=id,
        title=f"Task {id}",
        persona="backend",
        agent="codex",
        fallback=fallback,
        worktree=f".vibepod/worktrees/{id.lower()}",
        prompt_file=f".vibepod/tasks/{id.lower()}.md",
        depends_on=depends_on or [],
        status=status,
    )


# --- read_status_file ---

def test_read_status_file_done(tmp_path: Path):
    f = tmp_path / "status.json"
    f.write_text(json.dumps({"status": "done", "summary": "All good", "files_changed": []}))
    result = read_status_file(f)
    assert result.status == "done"
    assert result.summary == "All good"


def test_read_status_file_failed(tmp_path: Path):
    f = tmp_path / "status.json"
    f.write_text(json.dumps({"status": "failed", "reason": "Broke"}))
    result = read_status_file(f)
    assert result.status == "failed"
    assert result.reason == "Broke"


def test_read_status_file_missing_returns_none(tmp_path: Path):
    result = read_status_file(tmp_path / "missing.json")
    assert result is None


def test_read_status_file_malformed_returns_none(tmp_path: Path):
    f = tmp_path / "status.json"
    f.write_text("not json {{")
    result = read_status_file(f)
    assert result is None


def test_read_status_file_missing_status_key_returns_none(tmp_path: Path):
    f = tmp_path / "status.json"
    f.write_text(json.dumps({"summary": "something"}))
    result = read_status_file(f)
    assert result is None


# --- tasks_ready_to_run ---

def test_tasks_ready_no_deps():
    plan = OrchestratePlan(run_id="x", created="", task_description="t", tasks=[
        _make_task("TASK-1", status="pending"),
        _make_task("TASK-2", status="pending"),
    ])
    ready = tasks_ready_to_run(plan)
    assert {t.id for t in ready} == {"TASK-1", "TASK-2"}


def test_tasks_ready_respects_deps():
    plan = OrchestratePlan(run_id="x", created="", task_description="t", tasks=[
        _make_task("TASK-1", status="done"),
        _make_task("TASK-2", status="pending", depends_on=["TASK-1"]),
        _make_task("TASK-3", status="pending", depends_on=["TASK-2"]),
    ])
    ready = tasks_ready_to_run(plan)
    assert {t.id for t in ready} == {"TASK-2"}


def test_tasks_ready_skips_running():
    plan = OrchestratePlan(run_id="x", created="", task_description="t", tasks=[
        _make_task("TASK-1", status="running"),
        _make_task("TASK-2", status="pending"),
    ])
    ready = tasks_ready_to_run(plan)
    assert {t.id for t in ready} == {"TASK-2"}


def test_tasks_ready_blocked_if_dep_failed():
    plan = OrchestratePlan(run_id="x", created="", task_description="t", tasks=[
        _make_task("TASK-1", status="failed"),
        _make_task("TASK-2", status="pending", depends_on=["TASK-1"]),
    ])
    ready = tasks_ready_to_run(plan)
    assert ready == []


def test_tasks_ready_waiting_promoted_when_dep_done():
    plan = OrchestratePlan(run_id="x", created="", task_description="t", tasks=[
        _make_task("TASK-1", status="done"),
        _make_task("TASK-2", status="waiting", depends_on=["TASK-1"]),
    ])
    ready = tasks_ready_to_run(plan)
    assert {t.id for t in ready} == {"TASK-2"}


# --- poll_worker ---

def test_poll_worker_returns_done_when_status_file_appears(tmp_path: Path, monkeypatch):
    """poll_worker returns StatusResult(done) when status file is present immediately."""
    status_file = tmp_path / "status.json"
    status_file.write_text(json.dumps({"status": "done", "summary": "All good", "files_changed": []}))

    monkeypatch.setattr("vibepod.core.orchestrate.worker.time.sleep", lambda _: None)

    class _Container:
        status = "running"
        def reload(self): pass

    from vibepod.core.orchestrate.worker import poll_worker
    result = poll_worker(status_file, _Container(), poll_interval=0.0, timeout=10.0)
    assert result is not None
    assert result.status == "done"


def test_poll_worker_returns_failed_when_container_exits_without_file(tmp_path: Path, monkeypatch):
    """poll_worker returns failed when container exits without writing status file."""
    status_file = tmp_path / "missing.json"

    monkeypatch.setattr("vibepod.core.orchestrate.worker.time.sleep", lambda _: None)

    class _ExitedContainer:
        status = "exited"
        def reload(self): pass

    from vibepod.core.orchestrate.worker import poll_worker
    result = poll_worker(status_file, _ExitedContainer(), poll_interval=0.0, timeout=10.0)
    assert result is not None
    assert result.status == "failed"
    assert "exited" in result.reason or "status file" in result.reason


def test_poll_worker_returns_failed_when_container_unreachable(tmp_path: Path, monkeypatch):
    """poll_worker returns failed when container.reload() raises."""
    status_file = tmp_path / "missing.json"

    monkeypatch.setattr("vibepod.core.orchestrate.worker.time.sleep", lambda _: None)

    class _UnreachableContainer:
        status = "running"
        def reload(self):
            raise RuntimeError("connection lost")

    from vibepod.core.orchestrate.worker import poll_worker
    result = poll_worker(status_file, _UnreachableContainer(), poll_interval=0.0, timeout=10.0)
    assert result is not None
    assert result.status == "failed"


def test_poll_worker_returns_none_on_timeout(tmp_path: Path, monkeypatch):
    """poll_worker returns None when timeout expires."""
    status_file = tmp_path / "missing.json"

    monkeypatch.setattr("vibepod.core.orchestrate.worker.time.sleep", lambda _: None)

    # Patch monotonic to simulate timeout immediately
    call_count = {"n": 0}
    original_monotonic = __import__("time").monotonic

    def _fast_clock():
        call_count["n"] += 1
        # First call returns 0 (deadline = 0 + timeout), subsequent calls exceed deadline
        return 0.0 if call_count["n"] == 1 else 999.0

    monkeypatch.setattr("vibepod.core.orchestrate.worker.time.monotonic", _fast_clock)

    class _RunningContainer:
        status = "running"
        def reload(self): pass

    from vibepod.core.orchestrate.worker import poll_worker
    result = poll_worker(status_file, _RunningContainer(), poll_interval=0.0, timeout=1.0)
    assert result is None
