"""
Real-world integration test against provider APIs.

Usage:
    python scripts/test_providers.py

Results are saved to scripts/run/<streaming|non-streaming>/<provider>_<model>_<scenario>.json.

Configure which engines and scenarios to run by editing ENGINES, STREAMING, and SCENARIOS below.
API keys are read from environment variables (e.g. ANTHROPIC_API_KEY).
You can also place a .env file at the repo root.
"""

from __future__ import annotations

import asyncio
import json
import time
from dataclasses import asdict
from enum import Enum
from pathlib import Path

from sunwaee.modules.gen.engine.factory import get_engine
from sunwaee.modules.gen.engine.types import Message, Role, Tool, ToolCall

ENGINES: list[tuple[str, str]] = [
    # ("anthropic", "claude-haiku-4-5"),
    ("openai", "gpt-5-nano"),
    # ("deepseek", "deepseek-reasoner"),
    # ("google", "gemini-3-flash-preview"),
    # ("moonshot", "kimi-k2.5"),
    # ("xai", "grok-4-1-fast-reasoning"),
]

STREAMING: list[bool] = [False, True]

SCENARIOS: dict[str, list[Message]] = {
    "ONLY_SYSTEM": [
        Message(role=Role.SYSTEM, content="Respond with 'ok'."),
    ],
    "ONLY_USER": [
        Message(role=Role.USER, content="Respond with 'ok'."),
    ],
    # "COMPLEX": [
    #     Message(role=Role.USER, content="Tell me about nuclear fusion."),
    # ],
    "SYSTEM_AND_USER": [
        Message(role=Role.SYSTEM, content="Start all your answers with <START>."),
        Message(role=Role.USER, content="Respond with 'ok'."),
    ],
    "TOOL_CALL": [
        Message(role=Role.USER, content="Update this chat name to 'Test Chat'."),
    ],
    "TOOL_CALL_RESULT": [
        Message(role=Role.USER, content="Update this chat name to 'Test Chat'."),
        Message(
            role=Role.ASSISTANT,
            content="",
            ### NOTE deepseek reasoner or OpenAI compatible models
            reasoning_content="I will call the update_chat tool.",
            ### NOTE gemini: reasoning_signature -> thoughtSignature
            # reasoning_signature="ErsBCrgBAXLI2nw1imFQKoOEAlzRczX4nxtNt8xBvj0wP6sqGut5m59PGDGqExrghHfqux9ZV/4EZ7GnR3z2s+dZDT7CEr0sPRnykmcsTrsqiRPqitZjNKDH4Wtqbb/UjIyxTZK3tXa43jcyKXyRSbWsooVdmECgTNy+7rJZ+oZ51kBwrvSv1ZfUX0jntfNXEEQot9Sxxc5XkXtRaow2DuCKvyGpBS6/C9UD5dfQWHUJz0EKlrZoXSaBYaDPiA==",
            ### NOTE claude: reasoning_content -> thinking, reasoning_signature -> signature
            # reasoning_content="The user wants to update the chat name to 'Test Chat'. I have the `update_chat` function available which takes a `title` parameter. The user has provided the specific title they want: 'Test Chat'. I should use this exact value.\n\nI have all the required parameters, so I can proceed with the function call.",
            # reasoning_signature="EtoDCkYICxgCKkAK5CdwPz11XPu+QdxnaDZIP2/vnoLz5YdVzf4FK68A9Fk1bt7RQVtn5ba3o4tSUFsgB9AB+VogI3MPZO+C5BU9Egw1tb81X1wEDVcIK3EaDFEbxvHOfTxJFMO5VSIw/dAHMxnNdRA9fx77urY0aD7tFD2rHwr4zubEIwTqAvlMbxSTVlXSRioZvfT2e7qnKsECIgnuQXEJuWBjldkcDdTTr3JFfl5kDvFbcJPxFs9m9eDPRdvXOrgYiKzvbCoWHL7GiR3Gm0QWCV2oXVDMcO0C3+2ebmrSv5e7JjfBKtbq+k3DcazomHd8MuWouz0ojM55Aufy27iH+3rl1q/TrNKt4G431hPEz4BhrjipW2Qn157yi87GOqUKHWWefaKaPrQbZ0h2e5OMwVx932EtO7j3V4PznWd+E2U2Ah0zyZQBXQ/99pV0pv3px2gMRdQt6vH8P8Swa/du9SQ+WZHgGckc5EkFwXIF9DW8ib/Ec3lKFL97coqj35ZpJWP/z+fcbMsCS9efqQlSJST1Z4rsqB6qtzyntAQiDuPRHIRRKeENYG+iVHtuB4D8HaWCDs3ZygQZj6gEEKay1asqfLahc0XL3Tbcd7W1P4fOxmIN05SBVaaxGAE=",
            tool_calls=[
                ToolCall(
                    id="tc_1234567890",
                    name="update_chat",
                    arguments={"title": "Test Chat"},
                )
            ],
        ),
        Message(
            role=Role.TOOL,
            content='[{"before": "New Chat", "after": "Test Chat"}]',
            tool_call_id="tc_1234567890",
        ),
    ],
}

TOOLS: list[Tool] = [
    Tool(
        name="update_chat",
        description="Update the current chat title.",
        parameters={
            "type": "object",
            "properties": {
                "title": {"type": "string", "description": "The new chat title."},
            },
            "required": ["title"],
        },
        fn=lambda title: json.dumps({"before": "New Chat", "after": title}),
    ),
]

_TOOL_SCENARIOS = {"TOOL_CALL", "TOOL_CALL_RESULT"}

OUT_DIR = Path(__file__).parent / "run"


def _response_to_dict(obj) -> dict:
    """Recursively convert dataclasses to plain dicts for JSON serialisation."""
    if hasattr(obj, "__dataclass_fields__"):
        return {k: _response_to_dict(v) for k, v in asdict(obj).items()}
    if isinstance(obj, list):
        return [_response_to_dict(i) for i in obj]
    if isinstance(obj, Enum):
        return obj.value
    return obj


async def run_scenario(
    provider: str,
    model: str,
    scenario_name: str,
    messages: list[Message],
    streaming: bool,
) -> None:
    mode = "streaming" if streaming else "non-streaming"
    safe_model = model.replace("/", "-")
    out_path = OUT_DIR / mode / f"{provider}_{safe_model}_{scenario_name}.json"
    out_path.parent.mkdir(parents=True, exist_ok=True)

    tools = TOOLS if scenario_name in _TOOL_SCENARIOS else None

    try:
        engine = get_engine(provider, model)
    except ValueError as exc:
        print(f"  [SKIP] {exc}")
        out_path.write_text(json.dumps({"error": str(exc)}, indent=2))
        return

    t0 = time.perf_counter()
    try:
        if streaming:
            chunks = []
            async for chunk in engine.stream(messages, tools=tools):
                chunks.append(_response_to_dict(chunk))
            result = {"chunks": chunks}
        else:
            response = await engine.chat(messages, tools=tools)
            result = _response_to_dict(response)
    except Exception as exc:
        result = {"error": type(exc).__name__, "detail": str(exc)}

    elapsed_ms = int((time.perf_counter() - t0) * 1000)
    result["_meta"] = {
        "scenario": scenario_name,
    }

    out_path.write_text(json.dumps(result, indent=2))
    status = "ERROR" if result.get("error") else "OK"
    print(
        f"  [{elapsed_ms:>5}ms] {status:5}  {mode}/{provider}_{safe_model}_{scenario_name}.json"
    )


async def main() -> None:
    for streaming in STREAMING:
        for provider, model in ENGINES:
            label = f"{'stream' if streaming else 'chat':>6}  {provider}/{model}"
            print(f"\n{label}")
            for scenario_name, messages in SCENARIOS.items():
                await run_scenario(provider, model, scenario_name, messages, streaming)


if __name__ == "__main__":
    asyncio.run(main())
