from __future__ import annotations

import base64
from typing import Any
from urllib.parse import quote_plus

from rights_filter.analysis.preprocessing import ImagePayload
from rights_filter.domain.records import Evidence, EvidenceSource
from rights_filter.integrations.external_policy import ExternalApiPolicy
from rights_filter.integrations.http_json import UrllibJsonTransport


class FakeWebDetectionClient:
    def __init__(self, response: dict[str, Any] | None = None) -> None:
        self.response = response or {}
        self.calls: list[ImagePayload] = []

    def detect_web(self, image: ImagePayload) -> dict[str, Any]:
        self.calls.append(image)
        return self.response


class GoogleVisionRestClient:
    endpoint = "https://vision.googleapis.com/v1/images:annotate"

    def __init__(
        self,
        api_key: str,
        transport: Any | None = None,
        parent: str | None = None,
        max_results: int = 10,
        timeout: int = 15,
    ) -> None:
        self.api_key = api_key
        self.transport = transport or UrllibJsonTransport()
        self.parent = parent
        self.max_results = max_results
        self.timeout = timeout

    def detect_web(self, image: ImagePayload) -> dict[str, Any]:
        request: dict[str, Any] = {
            "image": {
                "content": base64.b64encode(image.content).decode("ascii"),
            },
            "features": [
                {
                    "type": "WEB_DETECTION",
                    "maxResults": self.max_results,
                }
            ],
        }
        payload: dict[str, Any] = {"requests": [request]}
        if self.parent:
            payload["parent"] = self.parent
        response = self.transport.request_json(
            "POST",
            f"{self.endpoint}?key={quote_plus(self.api_key)}",
            payload=payload,
            timeout=self.timeout,
        )
        web_detection = (response.get("responses") or [{}])[0].get("webDetection", {})
        return {
            "web_entities": [
                {
                    "description": item.get("description", ""),
                    "score": item.get("score", 0),
                    "entity_id": item.get("entityId", ""),
                }
                for item in web_detection.get("webEntities", [])
            ],
            "full_matching_images": [
                _web_image_result(item)
                for item in web_detection.get("fullMatchingImages", [])
            ],
            "partial_matching_images": [
                _web_image_result(item)
                for item in web_detection.get("partialMatchingImages", [])
            ],
            "visually_similar_images": [
                _web_image_result(item)
                for item in web_detection.get("visuallySimilarImages", [])
            ],
            "pages_with_matching_images": [
                _web_page_result(item)
                for item in web_detection.get("pagesWithMatchingImages", [])
            ],
            "best_guess_labels": web_detection.get("bestGuessLabels", []),
        }


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

    def detect(
        self, submission_id: str, derivative: ImagePayload, policy: ExternalApiPolicy
    ) -> list[Evidence]:
        allowed, reason = policy.can_call()
        if not allowed:
            return [
                Evidence(
                    source=EvidenceSource.EXTERNAL_SKIPPED,
                    reason=reason or "external API skipped",
                    confidence=1.0,
                    data={"submission_id": submission_id},
                )
            ]

        try:
            response = self.client.detect_web(derivative)
        except Exception as exc:  # pragma: no cover - defensive adapter boundary
            return [
                Evidence(
                    source=EvidenceSource.FAILURE,
                    reason=f"External API failed: {exc}",
                    confidence=1.0,
                    data={"submission_id": submission_id},
                )
            ]

        # Record the quota debit only after the request actually succeeds, so a
        # failed/timed-out call does not permanently consume the daily limit.
        policy.record_call()
        return _map_response(submission_id, response)


def _map_response(submission_id: str, response: dict[str, Any]) -> list[Evidence]:
    evidence: list[Evidence] = []

    for entity in response.get("web_entities", []):
        description = entity.get("description", "")
        # `or 0` guards an explicit JSON null (which float(None) would crash on);
        # clamp to [0, 1] because web-entity relevance scores are unbounded.
        score = max(0.0, min(1.0, float(entity.get("score", 0) or 0)))
        evidence.append(
            Evidence(
                source=EvidenceSource.WEB_DETECTION,
                reason=f"Web entity matched {description}",
                confidence=score,
                data={
                    "submission_id": submission_id,
                    "entity": description,
                    "entity_id": entity.get("entity_id", ""),
                    "score": score,
                    "provider": "google",
                    "match": "entity",
                },
            )
        )

    evidence.extend(
        _image_match_evidence(
            submission_id,
            response.get("full_matching_images", []),
            match="full",
            reason="Google full image match found",
            fallback_confidence=0.9,
        )
    )
    evidence.extend(
        _image_match_evidence(
            submission_id,
            response.get("partial_matching_images", []),
            match="partial",
            reason="Google partial image match found",
            fallback_confidence=0.75,
        )
    )
    evidence.extend(
        _image_match_evidence(
            submission_id,
            response.get("visually_similar_images", []),
            match="visual",
            reason="Google visually similar image found",
            fallback_confidence=0.55,
        )
    )

    for page in response.get("pages_with_matching_images", []):
        url = page.get("url", "")
        score = float(page.get("score", 0) or 0)
        evidence.append(
            Evidence(
                source=EvidenceSource.WEB_DETECTION,
                reason="Google page with matching image found",
                confidence=score or 0.75,
                data={
                    "submission_id": submission_id,
                    "url": url,
                    "result_url": url,
                    "page_title": page.get("page_title", ""),
                    "page_image_urls": _page_image_urls_from_page(page),
                    "score": score,
                    "match": "page",
                    "provider": "google",
                },
            )
        )

    for label in response.get("best_guess_labels", []):
        text = label.get("label", "")
        evidence.append(
            Evidence(
                source=EvidenceSource.WEB_DETECTION,
                reason=f"Google weak label {text}",
                confidence=0.0,
                data={
                    "submission_id": submission_id,
                    "label": text,
                    "weak_hint": True,
                    "provider": "google",
                    "match": "weak_label",
                },
            )
        )

    return evidence


def _web_image_result(item: dict[str, Any]) -> dict[str, Any]:
    result = {
        "url": _first_image_url(
            item,
            (
                "url",
                "imageUrl",
                "image_url",
                "imageurl",
                "contentUrl",
                "content_url",
                "contenturl",
                "src",
                "source",
                "mediaUrl",
                "media_url",
                "mediaurl",
                "thumbnailUrl",
                "thumbnail_url",
                "thumbnailurl",
                "thumbnail",
            ),
        ),
        "score": item.get("score", 0),
    }
    thumbnail_url = _first_image_url(
        item,
        ("thumbnailUrl", "thumbnail_url", "thumbnailurl", "thumbnail"),
    )
    if thumbnail_url:
        result["thumbnail_url"] = thumbnail_url
    return result


def _web_page_result(item: dict[str, Any]) -> dict[str, Any]:
    return {
        "url": item.get("url", ""),
        "score": item.get("score", 0),
        "page_title": item.get("pageTitle", item.get("page_title", "")),
        "page_image_urls": _page_image_urls_from_page(item),
    }


def _page_image_urls_from_page(page: dict[str, Any]) -> list[str]:
    candidates: list[str] = []
    candidates.extend(_image_values(page.get("page_image_urls", [])))
    for key in (
        "fullMatchingImages",
        "full_matching_images",
        "partialMatchingImages",
        "partial_matching_images",
        "visuallySimilarImages",
        "visually_similar_images",
    ):
        candidates.extend(_image_values(page.get(key, [])))
    for key in (
        "contentUrl",
        "content_url",
        "imageUrl",
        "image_url",
        "thumbnail",
        "thumbnailUrl",
        "thumbnail_url",
        "urlToImage",
        "url_to_image",
    ):
        candidates.extend(_image_values(page.get(key, "")))
    return _unique_texts(candidates)


def _image_values(value: Any) -> list[str]:
    if isinstance(value, str):
        return [value]
    if isinstance(value, list):
        values: list[str] = []
        for item in value:
            values.extend(_image_values(item))
        return values
    if isinstance(value, dict):
        values: list[str] = []
        for key in (
            "contentUrl",
            "content_url",
            "contenturl",
            "imageUrl",
            "image_url",
            "imageurl",
            "mediaUrl",
            "media_url",
            "mediaurl",
            "src",
            "source",
            "thumbnailUrl",
            "thumbnail_url",
            "thumbnailurl",
            "thumbnail",
            "url",
        ):
            if value.get(key):
                values.extend(_image_values(value[key]))
        return values
    return []


def _first_image_url(item: dict[str, Any], keys: tuple[str, ...]) -> str:
    for key in keys:
        if item.get(key):
            values = _image_values(item[key])
            if values:
                return values[0]
    return ""


def _unique_texts(values: list[str]) -> list[str]:
    seen: set[str] = set()
    result: list[str] = []
    for value in values:
        text = str(value or "").strip()
        if not text or text in seen:
            continue
        seen.add(text)
        result.append(text)
    return result


def _image_match_evidence(
    submission_id: str,
    images: list[dict[str, Any]],
    match: str,
    reason: str,
    fallback_confidence: float,
) -> list[Evidence]:
    evidence: list[Evidence] = []
    for image in images:
        url = _first_image_url(
            image,
            (
                "url",
                "imageUrl",
                "image_url",
                "imageurl",
                "contentUrl",
                "content_url",
                "contenturl",
                "src",
                "source",
                "mediaUrl",
                "media_url",
                "mediaurl",
                "thumbnailUrl",
                "thumbnail_url",
                "thumbnailurl",
                "thumbnail",
            ),
        )
        thumbnail_url = _first_image_url(
            image,
            ("thumbnailUrl", "thumbnail_url", "thumbnailurl", "thumbnail"),
        )
        score = float(image.get("score", 0) or 0)
        evidence.append(
            Evidence(
                source=EvidenceSource.WEB_DETECTION,
                reason=reason,
                confidence=score or fallback_confidence,
                data={
                    "submission_id": submission_id,
                    "url": url,
                    "image_url": url,
                    "thumbnail_url": thumbnail_url,
                    "score": score,
                    "match": match,
                    "provider": "google",
                },
            )
        )
    return evidence
