"""
Terminal API Router Tests
=========================
Phase 19.6: Router 層端點測試 (ADR-031)

測試策略:
- Pydantic 驗證測試 (422): 不需外部依賴
- 404 錯誤測試: 不需外部依賴
- POST /intent 成功路徑: 使用共享 TerminalService 實例
- GET /status + POST /abort: 需共享 TerminalService 實例

DI 覆寫設計:
  使用共享 TerminalService 避免跨請求 session 遺失。
  實作採無狀態設計 (session 儲存在 self._sessions)；
  生產環境依賴 Redis session 持久化 (ADR-031)。

遵循:
- feedback_no_mock_testing.md: 使用真實 TerminalService，非 MagicMock
- ADR-031: Omni-Terminal SSE Architecture
- ADR-024: Router 層只做 HTTP 轉發

Phase 19.6 ogt 2026-03-31 (台北時間)
"""

import pytest
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient

from src.api.v1.terminal import router
from src.core.csrf import verify_csrf_token
from src.models.terminal import TerminalSessionStatus
from src.services.terminal_service import TerminalService, get_terminal_service

# =============================================================================
# Test App 設定
# =============================================================================

# 共享 TerminalService 實例 (避免跨請求 session 遺失)
# 注意: 生產環境使用 Redis session 持久化；測試使用共享記憶體實例
_shared_service: TerminalService | None = None


async def _get_test_service() -> TerminalService:
    """注入共享 TerminalService 實例"""
    global _shared_service
    if _shared_service is None:
        _shared_service = TerminalService()
    return _shared_service


_test_app = FastAPI()
_test_app.include_router(router, prefix="/api/v1")
_test_app.dependency_overrides[get_terminal_service] = _get_test_service
_test_app.dependency_overrides[verify_csrf_token] = lambda: "test-bypass"  # tests have no browser session


@pytest.fixture(autouse=True)
def reset_service():
    """每個測試前重置共享 Service 狀態，避免 Session 汙染"""
    global _shared_service
    _shared_service = TerminalService()
    yield
    _shared_service = None


@pytest.fixture
async def client():
    """HTTP 測試客戶端 (ASGI Transport)"""
    async with AsyncClient(
        transport=ASGITransport(app=_test_app), base_url="http://test"
    ) as ac:
        yield ac


@pytest.fixture
def intent_payload():
    """標準 Intent 請求 payload"""
    return {
        "intent": "check system status",
        "context": {"current_page": "/dashboard"},
    }


# =============================================================================
# POST /terminal/intent - 提交意圖
# =============================================================================


@pytest.mark.asyncio
async def test_submit_intent_success(client, intent_payload):
    """成功提交意圖應返回 session_id + stream_url"""
    resp = await client.post("/api/v1/terminal/intent", json=intent_payload)
    assert resp.status_code == 200
    data = resp.json()
    assert "session_id" in data
    assert "stream_url" in data
    assert "created_at" in data


@pytest.mark.asyncio
async def test_submit_intent_stream_url_pattern(client, intent_payload):
    """stream_url 必須包含 session_id，格式為 /api/v1/terminal/stream/{session_id}"""
    resp = await client.post("/api/v1/terminal/intent", json=intent_payload)
    assert resp.status_code == 200
    data = resp.json()
    session_id = data["session_id"]
    assert session_id in data["stream_url"]
    assert data["stream_url"] == f"/api/v1/terminal/stream/{session_id}"


@pytest.mark.asyncio
async def test_submit_intent_missing_intent_field(client):
    """缺少必填 intent 欄位應返回 422"""
    resp = await client.post(
        "/api/v1/terminal/intent",
        json={"context": {"current_page": "/"}},
    )
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_submit_intent_missing_context_field(client):
    """缺少必填 context 欄位應返回 422"""
    resp = await client.post(
        "/api/v1/terminal/intent",
        json={"intent": "check status"},
    )
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_submit_intent_empty_string(client):
    """空 intent 字串應返回 422 (min_length=1)"""
    resp = await client.post(
        "/api/v1/terminal/intent",
        json={"intent": "", "context": {"current_page": "/"}},
    )
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_submit_intent_with_focused_entity(client):
    """帶 focused_entity_id 的 SpatialContext 應成功"""
    resp = await client.post(
        "/api/v1/terminal/intent",
        json={
            "intent": "analyze this incident",
            "context": {
                "current_page": "/incidents",
                "focused_entity_id": "INC-2026-0001",
            },
        },
    )
    assert resp.status_code == 200
    assert "session_id" in resp.json()


@pytest.mark.asyncio
async def test_submit_intent_with_session_id(client):
    """帶 session_id 的續傳請求應成功，且返回相同 session_id"""
    resp = await client.post(
        "/api/v1/terminal/intent",
        json={
            "intent": "continue analysis",
            "context": {"current_page": "/"},
            "session_id": "test-session-001",
        },
    )
    assert resp.status_code == 200
    assert resp.json()["session_id"] == "test-session-001"


@pytest.mark.asyncio
async def test_submit_intent_intent_too_long(client):
    """超過 max_length=2000 的 intent 應返回 422"""
    resp = await client.post(
        "/api/v1/terminal/intent",
        json={
            "intent": "a" * 2001,
            "context": {"current_page": "/"},
        },
    )
    assert resp.status_code == 422


# =============================================================================
# GET /terminal/status/{session_id} - 查詢狀態
# =============================================================================


@pytest.mark.asyncio
async def test_get_status_after_submit(client, intent_payload):
    """提交意圖後應能查詢 Session 狀態"""
    # 先提交意圖
    submit = await client.post("/api/v1/terminal/intent", json=intent_payload)
    assert submit.status_code == 200
    session_id = submit.json()["session_id"]

    # 查詢狀態
    status = await client.get(f"/api/v1/terminal/status/{session_id}")
    assert status.status_code == 200
    data = status.json()
    assert data["session_id"] == session_id
    assert data["status"] in [s.value for s in TerminalSessionStatus]
    assert "created_at" in data
    assert isinstance(data["last_event_id"], int)
    assert isinstance(data["message_count"], int)


@pytest.mark.asyncio
async def test_get_status_not_found(client):
    """不存在的 session_id 應返回 404"""
    resp = await client.get("/api/v1/terminal/status/nonexistent-session-id")
    assert resp.status_code == 404


@pytest.mark.asyncio
async def test_get_status_response_schema(client, intent_payload):
    """Status 回應必須包含所有必要欄位"""
    submit = await client.post("/api/v1/terminal/intent", json=intent_payload)
    session_id = submit.json()["session_id"]

    status = await client.get(f"/api/v1/terminal/status/{session_id}")
    data = status.json()

    required_fields = {"session_id", "status", "created_at", "last_event_id", "message_count"}
    assert required_fields.issubset(data.keys())


@pytest.mark.asyncio
async def test_get_status_initial_state(client, intent_payload):
    """新建立的 Session 狀態應為 PROCESSING (背景任務已啟動)"""
    submit = await client.post("/api/v1/terminal/intent", json=intent_payload)
    session_id = submit.json()["session_id"]

    status = await client.get(f"/api/v1/terminal/status/{session_id}")
    data = status.json()

    # 剛建立的 session 應為 PROCESSING (背景任務已啟動)
    # 或 COMPLETED 若背景任務已快速完成
    assert data["status"] in ["processing", "completed", "error"]


# =============================================================================
# POST /terminal/abort/{session_id} - 中斷執行
# =============================================================================


@pytest.mark.asyncio
async def test_abort_not_found(client):
    """中斷不存在的 session_id 應返回 404"""
    resp = await client.post("/api/v1/terminal/abort/nonexistent-session")
    assert resp.status_code == 404


@pytest.mark.asyncio
async def test_abort_with_reason_not_found(client):
    """帶理由中斷不存在的 session 應返回 404"""
    resp = await client.post(
        "/api/v1/terminal/abort/nonexistent-session",
        json={"reason": "user cancelled"},
    )
    assert resp.status_code == 404


@pytest.mark.asyncio
async def test_abort_existing_session(client, intent_payload):
    """中斷已存在的 session 應返回 200 (aborted=True)"""
    # 建立 session
    submit = await client.post("/api/v1/terminal/intent", json=intent_payload)
    assert submit.status_code == 200
    session_id = submit.json()["session_id"]

    # 中斷
    abort = await client.post(f"/api/v1/terminal/abort/{session_id}")
    assert abort.status_code == 200
    data = abort.json()
    assert data["session_id"] == session_id
    assert data["aborted"] is True
    assert "message" in data


@pytest.mark.asyncio
async def test_abort_with_reason(client, intent_payload):
    """帶理由的中斷應成功，message 包含理由"""
    submit = await client.post("/api/v1/terminal/intent", json=intent_payload)
    session_id = submit.json()["session_id"]

    abort = await client.post(
        f"/api/v1/terminal/abort/{session_id}",
        json={"reason": "user pressed Escape"},
    )
    assert abort.status_code == 200
    data = abort.json()
    assert data["aborted"] is True
    assert "user pressed Escape" in data["message"]


@pytest.mark.asyncio
async def test_abort_updates_session_status(client, intent_payload):
    """中斷後 Session 狀態應更新為 ABORTED"""
    submit = await client.post("/api/v1/terminal/intent", json=intent_payload)
    session_id = submit.json()["session_id"]

    # 中斷
    await client.post(f"/api/v1/terminal/abort/{session_id}")

    # 驗證狀態
    status = await client.get(f"/api/v1/terminal/status/{session_id}")
    assert status.status_code == 200
    assert status.json()["status"] == TerminalSessionStatus.ABORTED.value


# =============================================================================
# GET /terminal/stream/{session_id} - SSE 串流
# =============================================================================


@pytest.mark.asyncio
async def test_stream_session_not_found(client):
    """不存在的 session_id 的串流應返回 404 (在連接 Redis 之前)"""
    resp = await client.get("/api/v1/terminal/stream/nonexistent-stream-session")
    assert resp.status_code == 404
