#!/usr/bin/env python3
"""tests/fakes/ — FakeLLMClient, FakeDispatch 테스트 스위트 (M-24)

모리건(테스터) 작성 — TDD RED 단계
"""

from __future__ import annotations

import asyncio
import sys
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).parent.parent.parent))

from tests.fakes.fake_dispatch import FakeDispatch
from tests.fakes.fake_llm_client import FakeLLMClient


class TestFakeLLMClient:
    """FakeLLMClient 테스트"""

    def test_complete_returns_first_response(self) -> None:
        """첫 번째 응답 반환"""
        client = FakeLLMClient(responses=["hello", "world"])
        assert client.complete("prompt") == "hello"

    def test_complete_cycles_responses(self) -> None:
        """응답 목록을 순환"""
        client = FakeLLMClient(responses=["a", "b", "c"])
        assert client.complete("p1") == "a"
        assert client.complete("p2") == "b"
        assert client.complete("p3") == "c"
        assert client.complete("p4") == "a"  # 순환

    def test_complete_default_response_when_none(self) -> None:
        """responses=None이면 기본 응답 반환"""
        client = FakeLLMClient()
        result = client.complete("test")
        assert isinstance(result, str)
        assert len(result) > 0

    def test_call_count_increments(self) -> None:
        """호출 횟수 카운트"""
        client = FakeLLMClient(responses=["r"])
        assert client.call_count == 0
        client.complete("a")
        assert client.call_count == 1
        client.complete("b")
        assert client.call_count == 2

    def test_last_prompt_stored(self) -> None:
        """마지막 프롬프트 저장"""
        client = FakeLLMClient(responses=["x"])
        assert client.last_prompt is None
        client.complete("my prompt")
        assert client.last_prompt == "my prompt"

    def test_last_model_stored(self) -> None:
        """마지막 모델 저장"""
        client = FakeLLMClient(responses=["x"])
        assert client.last_model is None
        client.complete("p", model="custom-model")
        assert client.last_model == "custom-model"

    def test_reset_clears_state(self) -> None:
        """reset() 후 상태 초기화"""
        client = FakeLLMClient(responses=["a", "b"])
        client.complete("p")
        client.complete("p")
        client.reset()
        assert client.call_count == 0
        assert client.last_prompt is None
        assert client.last_model is None
        # 리셋 후 다시 첫 번째 응답부터
        assert client.complete("p") == "a"

    def test_set_responses_replaces(self) -> None:
        """set_responses()로 응답 목록 교체"""
        client = FakeLLMClient(responses=["old"])
        client.set_responses(["new1", "new2"])
        assert client.complete("p") == "new1"
        assert client.complete("p") == "new2"

    def test_complete_async_returns_coroutine(self) -> None:
        """complete_async는 코루틴 반환"""
        client = FakeLLMClient(responses=["async_result"])
        coro = client.complete_async("prompt")
        import inspect

        assert inspect.iscoroutine(coro)
        # 코루틴 닫기
        coro.close()

    def test_complete_async_returns_response(self) -> None:
        """complete_async 실행 결과 확인"""
        client = FakeLLMClient(responses=["async_hello"])

        async def run():
            return await client.complete_async("test")

        result = asyncio.run(run())
        assert result == "async_hello"

    def test_complete_with_kwargs(self) -> None:
        """추가 kwargs 전달 시 에러 없음"""
        client = FakeLLMClient(responses=["ok"])
        result = client.complete("p", model="m", temperature=0.7, max_tokens=100)
        assert result == "ok"


class TestFakeDispatch:
    """FakeDispatch 테스트"""

    def test_dispatch_returns_ok_status(self) -> None:
        """dispatch()는 status='ok' 반환"""
        fd = FakeDispatch()
        result = fd.dispatch("team-a", "task-1")
        assert result["status"] == "ok"

    def test_dispatch_returns_team(self) -> None:
        """dispatch() 결과에 team 포함"""
        fd = FakeDispatch()
        result = fd.dispatch("dev-team", "build something")
        assert result["team"] == "dev-team"

    def test_dispatch_returns_task_id(self) -> None:
        """dispatch() 결과에 task_id 포함"""
        fd = FakeDispatch()
        result = fd.dispatch("team", "task")
        assert "task_id" in result
        assert isinstance(result["task_id"], str)
        assert len(result["task_id"]) > 0

    def test_dispatch_count_increments(self) -> None:
        """dispatch_count 카운트"""
        fd = FakeDispatch()
        assert fd.dispatch_count == 0
        fd.dispatch("t", "task1")
        assert fd.dispatch_count == 1
        fd.dispatch("t", "task2")
        assert fd.dispatch_count == 2

    def test_last_team_stored(self) -> None:
        """마지막 team 저장"""
        fd = FakeDispatch()
        assert fd.last_team is None
        fd.dispatch("alpha", "x")
        assert fd.last_team == "alpha"

    def test_last_task_stored(self) -> None:
        """마지막 task 저장"""
        fd = FakeDispatch()
        assert fd.last_task is None
        fd.dispatch("t", "my-task")
        assert fd.last_task == "my-task"

    def test_dispatched_list_records_all(self) -> None:
        """dispatched 목록에 모든 위임 기록"""
        fd = FakeDispatch()
        fd.dispatch("t1", "task1")
        fd.dispatch("t2", "task2")
        assert len(fd.dispatched) == 2
        assert fd.dispatched[0]["team"] == "t1"
        assert fd.dispatched[1]["team"] == "t2"

    def test_set_failure_raises_exception(self) -> None:
        """set_failure(True) 시 dispatch()가 예외 발생"""
        fd = FakeDispatch()
        fd.set_failure(True)
        with pytest.raises(Exception):
            fd.dispatch("t", "task")

    def test_set_failure_false_restores(self) -> None:
        """set_failure(False)로 복구"""
        fd = FakeDispatch()
        fd.set_failure(True)
        fd.set_failure(False)
        result = fd.dispatch("t", "task")
        assert result["status"] == "ok"

    def test_reset_clears_state(self) -> None:
        """reset() 후 상태 초기화"""
        fd = FakeDispatch()
        fd.dispatch("t", "task")
        fd.set_failure(True)
        fd.reset()
        assert fd.dispatch_count == 0
        assert fd.last_team is None
        assert fd.last_task is None
        assert fd.dispatched == []
        # 리셋 후 실패 모드도 해제되어야 함
        result = fd.dispatch("t", "task")
        assert result["status"] == "ok"

    def test_dispatch_with_kwargs(self) -> None:
        """추가 kwargs 전달 시 에러 없음"""
        fd = FakeDispatch()
        result = fd.dispatch("team", "task", priority="high", deadline="2024-01-01")
        assert result["status"] == "ok"

    def test_unique_task_ids(self) -> None:
        """각 dispatch 호출마다 고유한 task_id"""
        fd = FakeDispatch()
        ids = [fd.dispatch("t", f"task{i}")["task_id"] for i in range(5)]
        assert len(set(ids)) == 5
