"""
Agent 4 — Next Steps Advisor

When the optimization loop exhausts all iterations without fully fixing the
prompt, this agent analyses everything that was tried and recommends concrete
next steps — which may be prompt changes, code changes, test changes,
process changes, or model changes.
"""
from __future__ import annotations

import json

import anthropic
from flask import current_app

from .types import IterationResult, NextStepsAdvice, NextStep


SYSTEM = """\
You are an expert at diagnosing why LLM prompt optimization fails and recommending actionable next steps for a developer.

You will be given the original prompt, all optimization attempts (what was tried, what still failed), and the remaining failures.

Your job: recommend 2–4 specific, concrete next steps the developer should take. These can span:
- PROMPT_CHANGE: a specific prompt modification not yet tried
- CODE_CHANGE: an upstream or downstream code change (e.g. output parser, input router, retry logic, structured output / tool_use API to enforce schema)
- TEST_CHANGE: changes to the eval suite (e.g. add more examples, clarify ambiguous expectations, split a test into focused sub-tests)
- PROCESS_CHANGE: a workflow or architectural change (e.g. add a human-review step, confidence threshold, fallback response)
- MODEL_CHANGE: switch model or use model-specific features (e.g. use claude-opus for complex reasoning, enable extended thinking)

Rules:
- Be specific and concrete. "Add a JSON extractor that strips text before the first { " is better than "fix the output format".
- If prompt-only changes were already tried 3 times and failed, do not recommend another prompt change as the primary step.
- Ground recommendations in the actual failure patterns — don't give generic advice.
- Order steps by impact: most likely to resolve the failure first.

Respond with valid JSON only, no markdown fences:
{
  "summary": "one sentence explaining why prompt-only changes could not fix this",
  "next_steps": [
    {
      "type": "PROMPT_CHANGE | CODE_CHANGE | TEST_CHANGE | PROCESS_CHANGE | MODEL_CHANGE",
      "title": "short action title (5–8 words)",
      "description": "specific, concrete description of exactly what to do and why it will help"
    }
  ]
}"""


def advise(
    original_prompt: str,
    history: list[IterationResult],
    model: str = "claude-sonnet-4-6",
) -> NextStepsAdvice:

    client = anthropic.Anthropic(api_key=current_app.config["ANTHROPIC_API_KEY"])

    user_message = _build_message(original_prompt, history)

    response = client.messages.create(
        model=model,
        max_tokens=2048,
        system=SYSTEM,
        messages=[{"role": "user", "content": user_message}],
    )

    return _parse(response.content[0].text)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _build_message(original_prompt: str, history: list[IterationResult]) -> str:
    parts = []

    parts.append(f"ORIGINAL PROMPT:\n\"\"\"\n{original_prompt}\n\"\"\"")

    parts.append("OPTIMIZATION ATTEMPTS:")
    for it in history:
        if it.screened_out:
            parts.append(
                f"  Iteration {it.iteration}: BLOCKED BY REGRESSION SCREENER\n"
                f"  Attempted: {it.candidate_fix.changes_summary}"
            )
        else:
            remaining = [tc.name for _, tc in it.remaining_failures]
            parts.append(
                f"  Iteration {it.iteration}:\n"
                f"  Attempted: {it.candidate_fix.changes_summary}\n"
                f"  Result: {it.pass_count} passed, {it.fail_count} still failing\n"
                f"  Still failing: {remaining}\n"
                f"  Root cause diagnosed: {'; '.join(it.root_cause.prompt_issues)}"
            )

    if history:
        last = history[-1]
        parts.append("REMAINING FAILURES (still failing after all iterations):")
        for result, tc in last.remaining_failures:
            inp = (tc.input_variables or {}).get("input", "")
            parts.append(
                f"  [{tc.name}]\n"
                f"  Input:    {inp}\n"
                f"  Expected: {tc.expected_output or tc.description or ''}\n"
                f"  Actual:   {result.actual_output or ''}\n"
                + (f"  Judge:    {result.judge_reasoning}\n" if result.judge_reasoning else "")
            )

    return "\n\n".join(parts)


def _parse(raw: str) -> NextStepsAdvice:
    raw = _strip_fences(raw)
    data = json.loads(raw)
    steps = [
        NextStep(
            type=s.get("type", "PROMPT_CHANGE"),
            title=s.get("title", ""),
            description=s.get("description", ""),
        )
        for s in data.get("next_steps", [])
    ]
    return NextStepsAdvice(
        summary=data.get("summary", ""),
        next_steps=steps,
    )


def _strip_fences(text: str) -> str:
    text = text.strip()
    if text.startswith("```"):
        lines = text.splitlines()
        inner = lines[1:-1] if lines[-1].strip() == "```" else lines[1:]
        if inner and inner[0].strip().lower() in ("json", ""):
            inner = inner[1:]
        text = "\n".join(inner).strip()
    return text
