import sys
from io import BytesIO
from types import SimpleNamespace

from rights_filter.analysis.face_person_detection import FacePersonSignal, HeuristicFacePersonDetector
from rights_filter.analysis.fingerprints import FingerprintService
from rights_filter.analysis.internal_analyzer import InternalAnalyzer
from rights_filter.analysis.preprocessing import ImagePayload
from rights_filter.domain.records import (
    EvidenceSource,
    InMemoryRightsFilterRepository,
    KnowledgeBaseEntry,
    KnowledgeEntryType,
    KnowledgeProvenance,
)


def _sample_png_bytes(color="white"):
    from PIL import Image, ImageDraw

    image = Image.new("RGB", (64, 64), color)
    draw = ImageDraw.Draw(image)
    draw.rectangle((14, 10, 46, 52), fill="black")
    buffer = BytesIO()
    image.save(buffer, format="PNG")
    return buffer.getvalue()


def test_fingerprint_service_treats_resized_same_image_as_similar():
    from PIL import Image, ImageDraw

    base = Image.new("RGB", (64, 64), "white")
    draw = ImageDraw.Draw(base)
    draw.rectangle((14, 10, 46, 52), fill="black")
    resized = base.resize((128, 128))

    base_buffer = BytesIO()
    resized_buffer = BytesIO()
    base.save(base_buffer, format="PNG")
    resized.save(resized_buffer, format="PNG")
    service = FingerprintService()

    base_fingerprint = service.fingerprints_for(base_buffer.getvalue())
    resized_fingerprint = service.fingerprints_for(resized_buffer.getvalue())

    assert service.similarity(base_fingerprint.perceptual, resized_fingerprint.perceptual) >= 0.9


def test_prior_rejected_similarity_emits_fingerprint_evidence():
    repo = InMemoryRightsFilterRepository()
    service = FingerprintService()
    payload = ImagePayload(content=_sample_png_bytes(), width=64, height=64, metadata={})
    fingerprints = service.fingerprints_for(payload.content)
    repo.save_knowledge_entry(
        KnowledgeBaseEntry.create(
            entry_type=KnowledgeEntryType.REJECTED_IMAGE,
            name="rejected:old",
            provenance=KnowledgeProvenance.AUTOMATIC_REJECTION,
            sample_fingerprints=[fingerprints.perceptual],
        )
    )
    analyzer = InternalAnalyzer(repo, service, HeuristicFacePersonDetector())

    evidence = analyzer.analyze("submission-1", payload)

    fingerprint_reasons = [item for item in evidence if item.source == EvidenceSource.FINGERPRINT]
    assert any("similarity" in item.reason for item in fingerprint_reasons)
    assert fingerprint_reasons[0].confidence >= 0.9


def test_watchlist_similarity_emits_separate_fingerprint_evidence_label():
    repo = InMemoryRightsFilterRepository()
    service = FingerprintService()
    payload = ImagePayload(content=_sample_png_bytes(), width=64, height=64, metadata={})
    fingerprints = service.fingerprints_for(payload.content)
    repo.save_knowledge_entry(
        KnowledgeBaseEntry.create(
            entry_type=KnowledgeEntryType.REJECTED_IMAGE,
            name="watchlist:old",
            provenance=KnowledgeProvenance.AUTOMATIC_REJECTION,
            sample_fingerprints=[fingerprints.perceptual],
            entry_status="watchlist",
            source_submission_id="SUB-OLD",
        )
    )
    analyzer = InternalAnalyzer(repo, service, HeuristicFacePersonDetector())

    evidence = analyzer.analyze("submission-1", payload)
    watchlist_matches = [
        item
        for item in evidence
        if item.source == EvidenceSource.FINGERPRINT
        and item.data.get("knowledge_entry_status") == "watchlist"
    ]

    assert watchlist_matches
    assert "주의 후보 이미지 유사도" in watchlist_matches[0].reason
    assert watchlist_matches[0].data["source_submission_id"] == "SUB-OLD"
    assert "identity" not in watchlist_matches[0].data
    assert "embedding" not in watchlist_matches[0].data


def test_face_presence_evidence_contains_no_identity_or_embedding():
    class OneFaceDetector:
        def detect(self, image):
            return FacePersonSignal(face_count=1, person_count=1)

    repo = InMemoryRightsFilterRepository()
    analyzer = InternalAnalyzer(repo, FingerprintService(), OneFaceDetector())
    payload = ImagePayload(content=b"image bytes", width=100, height=100, metadata={})

    evidence = analyzer.analyze("submission-2", payload)

    face_evidence = [item for item in evidence if item.source == EvidenceSource.FACE_PERSON]
    assert face_evidence
    assert face_evidence[0].data["face_count"] == 1
    assert "identity" not in face_evidence[0].data
    assert "embedding" not in face_evidence[0].data
    assert "face_boxes" not in face_evidence[0].data


def test_face_detector_does_not_treat_marker_text_as_detection():
    signal = HeuristicFacePersonDetector().detect(
        ImagePayload(content=b"portrait FACE PERSON", width=100, height=100, metadata={})
    )

    assert signal.present is False


def test_fingerprint_service_does_not_estimate_similarity_for_non_images():
    service = FingerprintService()

    left = service.fingerprints_for(b"same image content one")
    right = service.fingerprints_for(b"same image content two")

    assert left.perceptual.startswith("phash:unavailable:")
    assert service.similarity(left.perceptual, right.perceptual) == 0.0


def test_face_detector_uses_local_opencv_when_available(monkeypatch):
    class FakeCascade:
        def __init__(self, path):
            self.path = path

        def empty(self):
            return False

        def detectMultiScale(self, gray, scaleFactor, minNeighbors, minSize):
            if "frontalface" in self.path:
                return [(1, 2, 30, 30), (40, 2, 30, 30)]
            return []

    fake_cv2 = SimpleNamespace(
        IMREAD_COLOR=1,
        COLOR_BGR2GRAY=2,
        data=SimpleNamespace(haarcascades="cascades/"),
        imdecode=lambda data, flags: "decoded-image",
        cvtColor=lambda image, conversion: "gray-image",
        CascadeClassifier=FakeCascade,
    )
    fake_numpy = SimpleNamespace(
        uint8="uint8",
        frombuffer=lambda content, dtype: ["bytes"],
    )
    monkeypatch.setitem(sys.modules, "cv2", fake_cv2)
    monkeypatch.setitem(sys.modules, "numpy", fake_numpy)

    signal = HeuristicFacePersonDetector().detect(
        ImagePayload(content=b"\xff\xd8 image bytes", width=120, height=80, metadata={})
    )

    assert signal.face_count == 2
    assert signal.person_count == 2
    assert signal.face_boxes == ((1, 2, 30, 30), (40, 2, 30, 30))


def test_face_detector_falls_back_to_pillow_decode_for_avif(monkeypatch):
    class FakeCascade:
        def __init__(self, path):
            self.path = path

        def empty(self):
            return False

        def detectMultiScale(self, gray, scaleFactor, minNeighbors, minSize):
            return [(1, 2, 30, 30)] if "frontalface" in self.path else []

    class FakeImage:
        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc, traceback):
            return False

        def convert(self, mode):
            return "rgb-image"

    fake_cv2 = SimpleNamespace(
        IMREAD_COLOR=1,
        COLOR_BGR2GRAY=2,
        COLOR_RGB2BGR=3,
        data=SimpleNamespace(haarcascades="cascades/"),
        imdecode=lambda data, flags: None,
        cvtColor=lambda image, conversion: "gray-image",
        CascadeClassifier=FakeCascade,
    )
    fake_numpy = SimpleNamespace(
        uint8="uint8",
        frombuffer=lambda content, dtype: ["bytes"],
        array=lambda image: "array-image",
    )
    fake_pil_image = SimpleNamespace(open=lambda stream: FakeImage())
    fake_pil = SimpleNamespace(Image=fake_pil_image)
    monkeypatch.setitem(sys.modules, "cv2", fake_cv2)
    monkeypatch.setitem(sys.modules, "numpy", fake_numpy)
    monkeypatch.setitem(sys.modules, "PIL", fake_pil)
    monkeypatch.setitem(sys.modules, "PIL.Image", fake_pil_image)

    signal = HeuristicFacePersonDetector().detect(
        ImagePayload(content=b"avif image bytes", width=120, height=80, metadata={"format": "AVIF"})
    )

    assert signal.face_count == 1
    assert signal.person_count == 1
    assert signal.face_boxes == ((1, 2, 30, 30),)
