"""Anthropic Claude client — the Top-10 ranking stage (stage 9).

Takes the 20 GPT-4o ideas + voice profile + verified facts and returns a
ranked, filtered top-10 ranked topic list with reasoning + safety notes.

Uses Anthropic's Messages API: POST https://api.anthropic.com/v1/messages
with the x-api-key header (NOT Bearer).
"""

from __future__ import annotations

import json
import logging
from datetime import date
from typing import Any

import httpx

from app.config import settings
from app.models import RankedIdea, VoiceProfile
from app.providers._common import (
    JsonExtractionError,
    _build_semaphore,
    http_post_json,
    extract_json,
)

logger = logging.getLogger(__name__)

_ANTHROPIC_URL = "https://api.anthropic.com/v1/messages"
_ANTHROPIC_VERSION = "2023-06-01"


class AnthropicClient:
    def __init__(self) -> None:
        self._client = httpx.AsyncClient(timeout=settings.ANTHROPIC_TIMEOUT)
        self._sem = _build_semaphore(settings.ANTHROPIC_MAX_CONCURRENCY)

    async def close(self) -> None:
        await self._client.aclose()

    def _headers(self) -> dict[str, str]:
        return {
            "x-api-key": settings.ANTHROPIC_API_KEY,
            "anthropic-version": _ANTHROPIC_VERSION,
            "Content-Type": "application/json",
        }

    async def rank_top_ten(
        self,
        *,
        city: str,
        show_date: date,
        voice: VoiceProfile,
        verified_facts: list[dict[str, Any]],
        ideas: list[dict[str, Any]],
    ) -> list[RankedIdea]:
        system = (
            "You are a head writer for a touring comedian's road team. Given 20 "
            "candidate crowd-work ideas, you filter and rank them to the top 10 "
            "best fit for the host city. Ranking criteria: fit with the "
            "comedian's voice, ground in verified facts, on-stage safety, and "
            "audience engagement. DROP unsafe or unveriable ideas. Return STRICT JSON."
        )
        voice_block = json.dumps({
            "display_name": voice.display_name,
            "persona": voice.persona,
            "tone": voice.tone,
            "signature_bits": voice.signature_bits,
            "topics_to_lean_into": voice.topics_to_lean_into,
            "sensitivities_to_avoid": voice.sensitivities_to_avoid,
        }, ensure_ascii=False)
        user = (
            f"Show: {city} on {show_date.isoformat()}.\n"
            f"Voice profile JSON:\n{voice_block}\n"
            f"Verified facts JSON:\n{json.dumps(verified_facts, ensure_ascii=False, default=str)}\n"
            f"Candidate ideas JSON:\n{json.dumps(ideas, ensure_ascii=False, default=str)}\n"
            "Return STRICT JSON: an array of up to 10 objects with keys "
            '"title", "angle", "city_specificity", "risk_note" (nullable string), '
            '"rank" (integer 1..10, 1=best), "reasoning" (one sentence), '
            '"safety_notes" (nullable string). '
            "Do NOT include dropped ideas in the output."
        )
        payload = {
            "model": settings.ANTHROPIC_RANKING_MODEL,
            "max_tokens": 4096,
            "system": system,
            "messages": [{"role": "user", "content": user}],
            "temperature": 0.2,
        }
        async with self._sem:
            data = await http_post_json(
                client=self._client,
                url=_ANTHROPIC_URL,
                headers=self._headers(),
                payload=payload,
                timeout=float(settings.ANTHROPIC_TIMEOUT),
                provider_name="anthropic",
            )
        # Anthropic's shape: data['content'][0]['text']
        try:
            text_blocks = data.get("content", [])
            text = "".join(
                b.get("text", "") for b in text_blocks if b.get("type") == "text"
            )
        except (TypeError, AttributeError) as exc:
            raise JsonExtractionError(f"anthropic: bad content shape ({exc})") from exc
        try:
            parsed = extract_json(text)
        except JsonExtractionError:
            logger.warning("anthropic returned non-JSON; returning empty ranking")
            return []
        if isinstance(parsed, dict):
            array_source = next(
                (parsed[k] for k in ("ranked", "ideas", "top_ten", "topics")
                 if isinstance(parsed.get(k), list)),
                [],
            )
        elif isinstance(parsed, list):
            array_source = parsed
        else:
            array_source = []
        results: list[RankedIdea] = []
        rank = 1
        for item in sorted(array_source[:10], key=lambda x: int(x.get("rank", 10)) if isinstance(x, dict) else 10):
            if not isinstance(item, dict):
                continue
            results.append(RankedIdea(
                title=str(item.get("title") or "").strip()[:300],
                angle=str(item.get("angle") or "").strip()[:1000],
                city_specificity=str(item.get("city_specificity") or "").strip()[:500],
                risk_note=(str(item.get("risk_note")).strip()[:500]
                           if item.get("risk_note") else None),
                rank=int(item.get("rank") or rank),
                reasoning=(str(item.get("reasoning")).strip()[:500]
                           if item.get("reasoning") else None),
                safety_notes=(str(item.get("safety_notes")).strip()[:500]
                              if item.get("safety_notes") else None),
            ))
            rank += 1
        return results