# retoor <retoor@molodetz.nl>

import asyncio
import io
import re

import httpx
import imagehash
from PIL import Image

from devplacepy import net_guard

from .constants import (
    IMG_FETCH_MAX_BYTES,
    IMG_FETCH_TIMEOUT,
    MIN_IMAGE_DIMENSION,
    PHASH_DISTANCE,
)
from .models import ImageCandidate


def _decode_phash(data: bytes) -> tuple[str, int, int] | None:
    try:
        with Image.open(io.BytesIO(data)) as image:
            image.load()
            width, height = image.size
            phash = str(imagehash.phash(image))
        return phash, width, height
    except Exception:
        return None


async def _get_article_images(url: str, client: httpx.AsyncClient) -> list[dict]:
    try:
        await net_guard.guard_public_url(url)
    except Exception:
        return []
    try:
        resp = await client.get(url, timeout=10.0)
        resp.raise_for_status()
        html = resp.text
        pattern = re.compile(r'<img[^>]+src=["\']([^"\']+)["\']', re.IGNORECASE)
        matches = pattern.findall(html)
        images = []
        seen = set()
        for src in matches:
            if src in seen:
                continue
            seen.add(src)
            if src.startswith("http") and not any(
                ext in src.lower() for ext in [".svg", ".ico"]
            ):
                images.append({"url": src, "alt_text": ""})
        return images[:10]
    except Exception:
        return []


async def _fetch_image_candidate(
    src: dict, image_client: httpx.AsyncClient
) -> ImageCandidate:
    candidate = ImageCandidate(url=src["url"], alt_text=src.get("alt_text", ""))
    try:
        await net_guard.guard_public_url(candidate.url)
    except Exception:
        return candidate
    try:
        resp = await image_client.get(candidate.url, timeout=IMG_FETCH_TIMEOUT)
        resp.raise_for_status()
        data = resp.content[:IMG_FETCH_MAX_BYTES]
    except Exception:
        return candidate
    if len(data) >= IMG_FETCH_MAX_BYTES:
        return candidate
    loop = asyncio.get_running_loop()
    decoded = await loop.run_in_executor(None, _decode_phash, data)
    if decoded is None:
        return candidate
    phash, width, height = decoded
    candidate.phash = phash
    candidate.width = width
    candidate.height = height
    candidate.is_placeholder = (
        width < MIN_IMAGE_DIMENSION or height < MIN_IMAGE_DIMENSION
    )
    return candidate


def _flag_shared_placeholders(by_article: dict[str, list[ImageCandidate]]) -> None:
    entries: list[tuple[str, ImageCandidate, imagehash.ImageHash]] = []
    for article_uid, candidates in by_article.items():
        for candidate in candidates:
            if candidate.is_placeholder or not candidate.phash:
                continue
            entries.append(
                (article_uid, candidate, imagehash.hex_to_hash(candidate.phash))
            )
    for i in range(len(entries)):
        uid_a, cand_a, hash_a = entries[i]
        for j in range(i + 1, len(entries)):
            uid_b, cand_b, hash_b = entries[j]
            if uid_a == uid_b:
                continue
            if (hash_a - hash_b) <= PHASH_DISTANCE:
                cand_a.is_placeholder = True
                cand_b.is_placeholder = True


def _primary_image(candidates: list[ImageCandidate]) -> str:
    for candidate in candidates:
        if not candidate.is_placeholder:
            return candidate.url
    return ""
