"""Evaluation helpers for e2e agent tests.

Parses claude -p stream-json output and provides assertion utilities.
"""

from __future__ import annotations

import json
from dataclasses import dataclass, field


@dataclass
class AgentResult:
    """Parsed result from a claude -p stream-json invocation."""

    tools_called: list[str] = field(default_factory=list)
    tool_inputs: list[dict] = field(default_factory=list)
    tool_results: list[str] = field(default_factory=list)
    result_text: str = ""
    num_turns: int = 0
    cost_usd: float = 0.0
    is_error: bool = False
    error_message: str = ""
    duration_ms: int = 0


def parse_stream_json(raw_output: str) -> AgentResult:
    """Parse JSONL stream-json output from claude -p into an AgentResult."""
    result = AgentResult()

    for line in raw_output.strip().splitlines():
        line = line.strip()
        if not line:
            continue
        try:
            obj = json.loads(line)
        except json.JSONDecodeError:
            continue

        msg_type = obj.get("type", "")

        if msg_type == "assistant":
            message = obj.get("message", {})
            for block in message.get("content", []):
                if block.get("type") == "tool_use":
                    result.tools_called.append(block["name"])
                    result.tool_inputs.append(block.get("input", {}))

        elif msg_type == "user":
            message = obj.get("message", {})
            content = message.get("content", [])
            for block in content if isinstance(content, list) else []:
                if block.get("type") == "tool_result":
                    result.tool_results.append(str(block.get("content", "")))

        elif msg_type == "result":
            result.result_text = obj.get("result", "")
            result.num_turns = obj.get("num_turns", 0)
            result.cost_usd = obj.get("total_cost_usd", 0.0)
            result.is_error = obj.get("is_error", False)
            result.duration_ms = obj.get("duration_ms", 0)
            if result.is_error:
                result.error_message = obj.get("result", "")

    return result


def tool_was_called(result: AgentResult, tool_suffix: str) -> bool:
    """Check if a tool ending with the given suffix was called.

    Tool names in plugin mode have long prefixes like
    'mcp__plugin_luca-assistant_luca__luca_query_card_details',
    so we match on the suffix.
    """
    return any(name.endswith(tool_suffix) for name in result.tools_called)


def tools_called_suffixes(result: AgentResult) -> list[str]:
    """Extract just the short tool names (after last __)."""
    suffixes = []
    for name in result.tools_called:
        parts = name.rsplit("__", 1)
        suffixes.append(parts[-1] if len(parts) > 1 else name)
    return suffixes


def assert_tool_called(result: AgentResult, tool_suffix: str) -> None:
    """Assert that a tool matching the suffix was called."""
    called = tools_called_suffixes(result)
    assert tool_was_called(result, tool_suffix), (
        f"Expected tool '{tool_suffix}' to be called. "
        f"Actually called: {called}"
    )


def assert_tool_not_called(result: AgentResult, tool_suffix: str) -> None:
    """Assert that a tool matching the suffix was NOT called."""
    called = tools_called_suffixes(result)
    assert not tool_was_called(result, tool_suffix), (
        f"Expected tool '{tool_suffix}' NOT to be called, but it was. "
        f"Called tools: {called}"
    )


def assert_keywords_in_result(result: AgentResult, keywords: list[str]) -> None:
    """Assert all keywords appear in the result text (case-insensitive)."""
    text_lower = result.result_text.lower()
    missing = [kw for kw in keywords if kw.lower() not in text_lower]
    assert not missing, (
        f"Missing keywords in result: {missing}\n"
        f"Result text: {result.result_text[:500]}"
    )


def assert_any_keyword_in_result(result: AgentResult, keywords: list[str]) -> None:
    """Assert at least one keyword appears in the result text (case-insensitive)."""
    text_lower = result.result_text.lower()
    found = [kw for kw in keywords if kw.lower() in text_lower]
    assert found, (
        f"None of these keywords found in result: {keywords}\n"
        f"Result text: {result.result_text[:500]}"
    )


def assert_language(result: AgentResult, language: str) -> None:
    """Assert the response is predominantly in the expected language.

    Simple heuristic: for Chinese, check that a significant portion of
    characters are CJK. For English, check that most chars are ASCII.
    """
    text = result.result_text
    if not text:
        return

    if language == "chinese":
        cjk_count = sum(1 for ch in text if "\u4e00" <= ch <= "\u9fff")
        # At least 10 CJK characters in a non-trivial response
        assert cjk_count >= 10, (
            f"Expected Chinese response but found only {cjk_count} CJK characters.\n"
            f"Result: {text[:300]}"
        )
    elif language == "english":
        ascii_count = sum(1 for ch in text if ch.isascii())
        ratio = ascii_count / len(text) if text else 0
        assert ratio > 0.8, (
            f"Expected English response but ASCII ratio is {ratio:.2f}.\n"
            f"Result: {text[:300]}"
        )
