"""Core test runner: persona → target API → judge → report."""

from __future__ import annotations

import json
import time
from dataclasses import dataclass, field

import httpx

from housemonkey.personas import PERSONAS, get_persona


@dataclass
class TestResult:
    persona_name: str
    persona_display: str
    severity: str
    turns: list[dict] = field(default_factory=list)
    verdict: str = "untested"  # pass, fail, error
    failures: list[str] = field(default_factory=list)
    duration_ms: int = 0


def run_persona_against_target(
    persona_name: str,
    target_url: str,
    target_headers: dict | None = None,
    model: str = "gpt-4o-mini",
    api_key: str | None = None,
    verbose: bool = False,
    payload_template: str | None = None,
) -> TestResult:
    """Run a single persona against a target API endpoint.

    Target API must accept POST with JSON body:
    {"messages": [{"role": "user", "content": "..."}]}
    and return JSON with:
    {"choices": [{"message": {"content": "..."}}]}
    (OpenAI-compatible format)
    """
    persona = get_persona(persona_name)
    result = TestResult(
        persona_name=persona_name,
        persona_display=persona["name"],
        severity=persona["severity"],
    )

    start = time.monotonic()
    conversation: list[dict] = []

    # Generate persona messages using LLM
    for turn in range(persona["turns"]):
        # Build persona prompt
        persona_messages = [
            {"role": "system", "content": persona["system_prompt"]},
        ]
        if conversation:
            # Add conversation history so persona can react to target's responses
            for msg in conversation:
                persona_messages.append(msg)
            persona_messages.append(
                {"role": "user", "content": "Continue the conversation. Send your next message as the persona. Output ONLY the message text."}
            )

        # Generate persona message via OpenAI-compatible API
        try:
            persona_msg = _call_llm(
                messages=persona_messages,
                model=model,
                api_key=api_key,
            )
        except Exception as e:
            result.verdict = "error"
            result.failures.append(f"Persona LLM error: {e}")
            break

        # Send to target
        conversation.append({"role": "user", "content": persona_msg})

        try:
            target_response = _call_target(
                url=target_url,
                messages=conversation,
                headers=target_headers,
                payload_template=payload_template,
            )
        except Exception as e:
            result.verdict = "error"
            result.failures.append(f"Target API error on turn {turn + 1}: {e}")
            break

        conversation.append({"role": "assistant", "content": target_response})
        result.turns.append({
            "turn": turn + 1,
            "persona_said": persona_msg,
            "target_said": target_response,
        })

        if verbose:
            print(f"  Turn {turn + 1}/{persona['turns']}")
            print(f"    Persona: {persona_msg[:100]}...")
            print(f"    Target:  {target_response[:100]}...")

    result.duration_ms = int((time.monotonic() - start) * 1000)
    return result


def _call_llm(
    messages: list[dict],
    model: str = "gpt-4o-mini",
    api_key: str | None = None,
) -> str:
    """Call OpenAI-compatible API to generate text."""
    import os
    key = api_key or os.environ.get("OPENAI_API_KEY") or os.environ.get("ANTHROPIC_API_KEY")
    if not key:
        raise ValueError("No API key found. Set OPENAI_API_KEY or pass --api-key")

    # Detect provider from key prefix
    if key.startswith("sk-ant"):
        return _call_anthropic(messages, model="claude-3-5-sonnet-20241022", api_key=key)

    resp = httpx.post(
        "https://api.openai.com/v1/chat/completions",
        headers={"Authorization": f"Bearer {key}"},
        json={"model": model, "messages": messages, "max_tokens": 500},
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()["choices"][0]["message"]["content"]


def _call_anthropic(messages: list[dict], model: str, api_key: str) -> str:
    """Call Anthropic API."""
    system = ""
    msgs = []
    for m in messages:
        if m["role"] == "system":
            system = m["content"]
        else:
            msgs.append(m)

    resp = httpx.post(
        "https://api.anthropic.com/v1/messages",
        headers={
            "x-api-key": api_key,
            "anthropic-version": "2023-06-01",
            "content-type": "application/json",
        },
        json={
            "model": model,
            "system": system,
            "messages": msgs,
            "max_tokens": 500,
        },
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()["content"][0]["text"]


def _call_target(
    url: str,
    messages: list[dict],
    headers: dict | None = None,
    payload_template: str | None = None,
) -> str:
    """Call target API. Supports OpenAI format or custom payload template."""
    h = {"Content-Type": "application/json"}
    if headers:
        h.update(headers)

    if payload_template:
        # Custom payload: replace {{message}} with last user message
        import json as json_mod
        last_msg = messages[-1]["content"] if messages else ""
        payload_str = payload_template.replace("{{message}}", last_msg)
        payload = json_mod.loads(payload_str)
    else:
        payload = {"messages": messages}

    resp = httpx.post(url, headers=h, json=payload, timeout=30)
    resp.raise_for_status()

    # Try to parse as JSON
    try:
        data = resp.json()
    except Exception:
        return resp.text

    # Try OpenAI format
    if "choices" in data:
        return data["choices"][0]["message"]["content"]
    # Try Anthropic format
    if "content" in data and isinstance(data["content"], list):
        return data["content"][0]["text"]
    # Try common custom formats
    for key in ("response", "text", "answer", "output", "reply", "result"):
        if key in data and isinstance(data[key], str):
            return data[key]

    # Last resort: return full JSON as string
    return str(data)
