"""Tests for ApiHookClient.wait_for_project_switch.

These tests use mocked _make_request so they don't require a live_gui
session. They verify the polling logic: success, error, queued-not-returned,
timeout, and path-matching behavior.

Contract: wait_for_project_switch returns when the controller's switch
has completed (in_progress=False) and the path matches. It does NOT
return on queued switches (in_progress=True) because queued means a
prior switch is still running, and downstream code (AI ops) will be
blocked by is_project_stale().
"""
import sys
import os
import time
from unittest.mock import patch

sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

from src.api_hook_client import ApiHookClient


def test_wait_for_project_switch_returns_immediately_when_idle() -> None:
 """If the controller is already idle and path matches, return immediately."""
 client = ApiHookClient()
 with patch.object(client, "_make_request") as mock_make:
  mock_make.return_value = {"in_progress": False, "path": "C:/projects/foo.toml", "error": None}
  start = time.time()
  result = client.wait_for_project_switch(expected_path="C:/projects/foo.toml", timeout=5.0)
  elapsed = time.time() - start
  assert result["in_progress"] is False
  assert result["path"] == "C:/projects/foo.toml"
  assert result.get("error") is None
  assert "timeout" not in result
  assert elapsed < 1.0, f"should return quickly, took {elapsed}s"


def test_wait_for_project_switch_surfaces_error() -> None:
 """If the controller reports an error, return immediately with the error."""
 client = ApiHookClient()
 with patch.object(client, "_make_request") as mock_make:
  mock_make.return_value = {"in_progress": False, "path": None, "error": "load failed: file not found"}
  result = client.wait_for_project_switch(expected_path="C:/missing.toml", timeout=5.0)
  assert result["error"] == "load failed: file not found"
  assert "timeout" not in result


def test_wait_for_project_switch_matches_by_basename() -> None:
 """Path matching is loose: matches by basename to handle absolute vs relative."""
 client = ApiHookClient()
 with patch.object(client, "_make_request") as mock_make:
  mock_make.return_value = {"in_progress": False, "path": "C:/abs/path/temp_project.toml", "error": None}
  result = client.wait_for_project_switch(
   expected_path="tests/artifacts/temp_project.toml",
   timeout=5.0,
  )
  assert result["path"].endswith("temp_project.toml")
  assert "timeout" not in result


def test_wait_for_project_switch_does_not_return_on_queued() -> None:
 """If in_progress=True and path matches (switch is queued), keep waiting.

 The prior switch is hung. The new switch is queued behind it. Returning
 now would mislead the caller into thinking the switch is done, but
 is_project_stale() will still return True and block AI ops.
 """
 client = ApiHookClient()
 with patch.object(client, "_make_request") as mock_make:
  mock_make.return_value = {"in_progress": True, "path": "C:/projects/foo.toml", "error": None}
  result = client.wait_for_project_switch(expected_path="C:/projects/foo.toml", timeout=0.5, poll_interval=0.1)
  # Should time out, not return early
  assert result.get("timeout") is True
  assert result["in_progress"] is True


def test_wait_for_project_switch_times_out_when_in_progress() -> None:
 """If in_progress stays True and path never matches, return with timeout flag."""
 client = ApiHookClient()
 with patch.object(client, "_make_request") as mock_make:
  mock_make.return_value = {"in_progress": True, "path": None, "error": None}
  result = client.wait_for_project_switch(expected_path="C:/foo.toml", timeout=0.5, poll_interval=0.1)
  assert result.get("timeout") is True
  assert result["in_progress"] is True


def test_wait_for_project_switch_no_expected_path() -> None:
 """If expected_path is None, return when in_progress becomes False (any path)."""
 client = ApiHookClient()
 with patch.object(client, "_make_request") as mock_make:
  mock_make.return_value = {"in_progress": False, "path": "C:/anything.toml", "error": None}
  result = client.wait_for_project_switch(expected_path=None, timeout=5.0)
  assert result["in_progress"] is False
  assert "timeout" not in result


def test_wait_for_project_switch_polls_then_completes() -> None:
 """Simulate a switch that takes 2 polls to complete."""
 client = ApiHookClient()
 call_count = [0]

 def fake_request(*args, **kwargs):
  call_count[0] += 1
  if call_count[0] < 3:
   return {"in_progress": True, "path": "C:/other.toml", "error": None}
  return {"in_progress": False, "path": "C:/foo.toml", "error": None}

 with patch.object(client, "_make_request", side_effect=fake_request):
  result = client.wait_for_project_switch(expected_path="C:/foo.toml", timeout=5.0, poll_interval=0.05)
  assert result["in_progress"] is False
  assert result["path"] == "C:/foo.toml"
  assert "timeout" not in result
  assert call_count[0] >= 3
