from __future__ import annotations

from dataclasses import dataclass
from typing import Any

from rights_filter.domain.records import Evidence, EvidenceSource
from rights_filter.integrations.http_json import UrllibJsonTransport


@dataclass
class FakeInternalLlmClient:
    summary: str = ""
    error: Exception | None = None

    def summarize_evidence(self, evidence: list[Evidence]) -> str:
        if self.error:
            raise self.error
        return self.summary or "Evidence summary unavailable."


class OllamaGenerateLlmClient:
    def __init__(
        self,
        base_url: str = "http://127.0.0.1:11434",
        model: str = "qwen2.5:0.5b-instruct",
        transport: Any | None = None,
        timeout: int = 30,
        connect_timeout: float = 2,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.model = model
        # Fast-fail the connect (e.g. a local LLM that isn't running) so seed /
        # reload requests are not stalled for the full read timeout, while real
        # generation still gets the full `timeout`.
        self.transport = transport or UrllibJsonTransport(connect_timeout=connect_timeout)
        self.timeout = timeout

    def summarize_evidence(self, evidence: list[Evidence]) -> str:
        payload = {
            "model": self.model,
            "system": (
                "Summarize only the provided source evidence for an internal image "
                "rights review operator. Do not make a final decision. Do not add "
                "claims that are not grounded in source evidence."
            ),
            "prompt": _evidence_prompt(evidence),
            "stream": False,
            "options": {"temperature": 0.1},
        }
        response = self.transport.request_json(
            "POST",
            self._generate_url(),
            payload=payload,
            timeout=self.timeout,
        )
        return _response_text(response)

    def _generate_url(self) -> str:
        if self.base_url.endswith("/api"):
            return f"{self.base_url}/generate"
        return f"{self.base_url}/api/generate"


class InternalLlmAssistant:
    def __init__(self, client: Any) -> None:
        self.client = client

    def summarize(self, submission_id: str, evidence: list[Evidence]) -> Evidence:
        try:
            summary = self.client.summarize_evidence(evidence)
        except Exception as exc:
            return Evidence(
                source=EvidenceSource.ENRICHMENT_FAILURE,
                reason=f"LLM assistance failed: {exc}",
                confidence=1.0,
                data={"submission_id": submission_id},
            )

        source_urls = _source_urls(evidence)
        source_evidence_ids = _source_evidence_ids(evidence)
        if not source_urls and not source_evidence_ids:
            return Evidence(
                source=EvidenceSource.LLM_SUMMARY,
                reason="LLM summary has no source evidence",
                confidence=0.0,
                data={
                    "submission_id": submission_id,
                    "summary": summary,
                    "source_urls": [],
                    "source_evidence_ids": [],
                    "verified": False,
                },
            )

        return Evidence(
            source=EvidenceSource.LLM_SUMMARY,
            reason="Assistant summarized source-linked evidence",
            confidence=0.0,
            data={
                "submission_id": submission_id,
                "summary": summary,
                "source_urls": source_urls,
                "source_evidence_ids": source_evidence_ids,
                "verified": True,
            },
        )


def _source_urls(evidence: list[Evidence]) -> list[str]:
    urls: list[str] = []
    for item in evidence:
        for key in ("result_url", "url", "image_url"):
            value = item.data.get(key)
            if value and value not in urls:
                urls.append(str(value))
    return urls


def _source_evidence_ids(evidence: list[Evidence]) -> list[str]:
    ids: list[str] = []
    for item in evidence:
        value = item.data.get("evidence_id")
        if value and value not in ids:
            ids.append(str(value))
    return ids


def _evidence_prompt(evidence: list[Evidence]) -> str:
    lines = ["Source evidence:"]
    for index, item in enumerate(evidence, start=1):
        lines.append(
            f"{index}. source={item.source.value}; reason={item.reason}; "
            f"confidence={item.confidence}; data={item.data}"
        )
    return "\n".join(lines)


def _response_text(response: dict[str, Any]) -> str:
    if response.get("response"):
        return str(response["response"])
    if response.get("output_text"):
        return str(response["output_text"])
    chunks: list[str] = []
    for item in response.get("output", []):
        for content in item.get("content", []):
            text = content.get("text")
            if text:
                chunks.append(str(text))
    return "\n".join(chunks) or "Evidence summary unavailable."
