from __future__ import annotations

import hashlib
from io import BytesIO
from dataclasses import dataclass


@dataclass(frozen=True)
class Fingerprints:
    exact: str
    perceptual: str


class FingerprintService:
    def fingerprints_for(self, content: bytes) -> Fingerprints:
        exact_digest = hashlib.sha256(content).hexdigest()
        exact = "exact:" + exact_digest
        perceptual = "phash:" + _perceptual_hash(content, exact_digest)
        return Fingerprints(exact=exact, perceptual=perceptual)

    def similarity(self, left: str, right: str) -> float:
        if left == right:
            return 1.0
        left_value = left.split(":", 1)[-1]
        right_value = right.split(":", 1)[-1]
        if left_value.startswith("unavailable:") or right_value.startswith("unavailable:"):
            return 0.0
        if _looks_like_64bit_hash(left_value) and _looks_like_64bit_hash(right_value):
            distance = (int(left_value, 16) ^ int(right_value, 16)).bit_count()
            return 1 - (distance / 64)
        return 0.0


def _perceptual_hash(content: bytes, exact_digest: str) -> str:
    try:
        from PIL import Image

        with Image.open(BytesIO(content)) as image:
            thumbnail = image.convert("L").resize((8, 8))
            if hasattr(thumbnail, "get_flattened_data"):
                pixels = list(thumbnail.get_flattened_data())
            else:
                pixels = list(thumbnail.getdata())
    except Exception:
        return f"unavailable:{exact_digest}"

    average = sum(pixels) / len(pixels)
    bits = 0
    for pixel in pixels:
        bits = (bits << 1) | int(pixel >= average)
    return f"{bits:016x}"


def _looks_like_64bit_hash(value: str) -> bool:
    if len(value) != 16:
        return False
    try:
        int(value, 16)
    except ValueError:
        return False
    return True
