from pathlib import Path
import base64
import hashlib
import json
import sqlite3
from io import BytesIO

import pytest

from rights_filter.analysis.face_person_detection import FacePersonSignal
from rights_filter.analysis.preprocessing import ImagePayload, build_face_crop_derivatives
from rights_filter.domain.records import Evidence, EvidenceSource
from rights_filter.server.image_store import LocalSubmissionImageStore
from rights_filter.server import sqlite_store as sqlite_store_module
from rights_filter.server.sqlite_store import CopyrighterStore
from rights_filter.integrations.env_clients import build_provider_runtime


class FakeJsonTransport:
    def __init__(self, response):
        self.response = response
        self.calls = []

    def request_json(self, method, url, headers=None, payload=None, timeout=10):
        self.calls.append({"method": method, "url": url, "headers": headers or {}, "payload": payload})
        return self.response


class RoutingFakeJsonTransport:
    def __init__(
        self,
        google_response,
        naver_response,
        llm_response=None,
        naver_blog_response=None,
        naver_web_response=None,
        google_custom_image_response=None,
        google_custom_web_response=None,
    ):
        self.google_response = google_response
        self.naver_response = naver_response
        self.naver_blog_response = naver_blog_response or {"items": []}
        self.naver_web_response = naver_web_response or {"items": []}
        self.google_custom_image_response = google_custom_image_response or {"items": []}
        self.google_custom_web_response = google_custom_web_response or {"items": []}
        self.llm_response = llm_response or {"response": "summary"}
        self.calls = []

    def request_json(self, method, url, headers=None, payload=None, timeout=10):
        self.calls.append({"method": method, "url": url, "headers": headers or {}, "payload": payload})
        if "www.googleapis.com/customsearch/v1" in url:
            if "searchType=image" in url:
                return self.google_custom_image_response
            return self.google_custom_web_response
        if "vision.googleapis.com" in url:
            return self.google_response
        if "openapi.naver.com/v1/search/blog" in url:
            return self.naver_blog_response
        if "openapi.naver.com/v1/search/webkr" in url:
            return self.naver_web_response
        if "openapi.naver.com" in url:
            return self.naver_response
        return self.llm_response


class SequencedRoutingFakeJsonTransport(RoutingFakeJsonTransport):
    def __init__(self, google_responses, **kwargs):
        super().__init__(
            google_response=google_responses[-1] if google_responses else {"responses": [{}]},
            **kwargs,
        )
        self.google_responses = list(google_responses)
        self.google_call_count = 0

    def request_json(self, method, url, headers=None, payload=None, timeout=10):
        self.calls.append({"method": method, "url": url, "headers": headers or {}, "payload": payload})
        if "vision.googleapis.com" in url:
            index = min(self.google_call_count, len(self.google_responses) - 1)
            self.google_call_count += 1
            return self.google_responses[index]
        if "www.googleapis.com/customsearch/v1" in url:
            if "searchType=image" in url:
                return self.google_custom_image_response
            return self.google_custom_web_response
        if "openapi.naver.com/v1/search/blog" in url:
            return self.naver_blog_response
        if "openapi.naver.com/v1/search/webkr" in url:
            return self.naver_web_response
        if "openapi.naver.com" in url:
            return self.naver_response
        return self.llm_response


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


@pytest.fixture(autouse=True)
def use_explicit_test_face_detector(monkeypatch):
    monkeypatch.setattr(sqlite_store_module, "HeuristicFacePersonDetector", lambda: OneFaceDetector())


def _image_store(tmp_path: Path) -> LocalSubmissionImageStore:
    image_dir = tmp_path / "images"
    image_dir.mkdir(parents=True)
    (image_dir / "sample.svg").write_text(
        "<svg><!-- FACE PERSON --></svg>",
        encoding="utf-8",
    )
    (tmp_path / "submissions.json").write_text(
        """
        [
          {
            "id": "SUB-DB1",
            "title": "DB sample",
            "file": "images/sample.svg",
            "width": 800,
            "height": 600,
            "submitted_at": "2026-05-26 10:30"
          }
        ]
        """,
        encoding="utf-8",
    )
    return LocalSubmissionImageStore(tmp_path)


def _png_image_store(tmp_path: Path) -> LocalSubmissionImageStore:
    from PIL import Image

    image_dir = tmp_path / "images"
    image_dir.mkdir(parents=True)
    source = BytesIO()
    Image.new("RGB", (160, 120), color=(230, 220, 210)).save(source, format="PNG")
    (image_dir / "portrait.png").write_bytes(source.getvalue())
    (tmp_path / "submissions.json").write_text(
        """
        [
          {
            "id": "SUB-PNG1",
            "title": "Portrait sample",
            "file": "images/portrait.png",
            "width": 160,
            "height": 120,
            "submitted_at": "2026-05-26 10:30"
          }
        ]
        """,
        encoding="utf-8",
    )
    return LocalSubmissionImageStore(tmp_path)


def _face_crop_photo_store(tmp_path: Path) -> LocalSubmissionImageStore:
    from PIL import Image, ImageDraw

    image_dir = tmp_path / "images"
    image_dir.mkdir(parents=True)
    source = BytesIO()
    image = Image.new("RGB", (400, 300), color=(10, 10, 10))
    draw = ImageDraw.Draw(image)
    for x in range(0, 400, 20):
        color = (250, 250, 250) if x % 40 == 0 else (30, 30, 30)
        draw.rectangle((x, 0, x + 10, 300), fill=color)
    draw.rectangle((150, 80, 230, 160), fill=(100, 100, 100))
    draw.ellipse((170, 90, 210, 130), fill=(220, 180, 140))
    draw.rectangle((160, 140, 220, 155), fill=(0, 80, 200))
    image.save(source, format="PNG")
    (image_dir / "face-scene.png").write_bytes(source.getvalue())
    (tmp_path / "submissions.json").write_text(
        json.dumps(
            [
                {
                    "id": "SUB-FACE1",
                    "title": "Face crop scene",
                    "file": "images/face-scene.png",
                    "width": 400,
                    "height": 300,
                    "submitted_at": "2026-05-26 10:30",
                }
            ],
            ensure_ascii=False,
        ),
        encoding="utf-8",
    )
    return LocalSubmissionImageStore(tmp_path)


def _titled_image_store(tmp_path: Path, title: str, filename: str = "images/portrait.svg") -> LocalSubmissionImageStore:
    image_dir = tmp_path / "images"
    image_dir.mkdir(parents=True)
    (tmp_path / filename).write_text(
        "<svg><!-- FACE PERSON --></svg>",
        encoding="utf-8",
    )
    (tmp_path / "submissions.json").write_text(
        json.dumps(
            [
                {
                    "id": "SUB-TITLE1",
                    "title": title,
                    "file": filename,
                    "width": 800,
                    "height": 600,
                    "submitted_at": "2026-05-26 10:30",
                }
            ],
            ensure_ascii=False,
        ),
        encoding="utf-8",
    )
    return LocalSubmissionImageStore(tmp_path)


def _candidate_png_bytes(
    color: tuple[int, int, int] = (210, 220, 230),
    marker: str = "",
) -> bytes:
    from PIL import Image, ImageDraw

    buffer = BytesIO()
    image = Image.new("RGB", (80, 60), color=color)
    draw = ImageDraw.Draw(image)
    if marker == "left":
        draw.rectangle((0, 0, 36, 60), fill=(20, 50, 90))
        draw.rectangle((36, 0, 80, 20), fill=(235, 235, 245))
    elif marker == "right":
        draw.rectangle((44, 0, 80, 60), fill=(120, 40, 30))
        draw.ellipse((8, 8, 42, 42), fill=(245, 245, 235))
    image.save(buffer, format="PNG")
    return buffer.getvalue()


def test_sqlite_store_seeds_review_data_from_image_store(tmp_path: Path):
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    bootstrap = store.bootstrap()

    assert bootstrap["submissions"][0]["id"] == "SUB-DB1"
    assert bootstrap["submissions"][0]["asset"] == "/media/images/sample.svg"
    assert bootstrap["submissions"][0]["riskBand"] in {"low", "medium", "high"}
    assert bootstrap["providers"][0]["id"] == "internal"
    assert bootstrap["auditEvents"][0]["event"] == "Analysis run created"


def test_sqlite_store_bootstrap_includes_search_coverage(tmp_path: Path):
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3")
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    bootstrap = store.bootstrap()
    coverage = bootstrap["searchCoverage"]

    assert coverage["submissions"]["total"] == 1
    assert coverage["submissions"]["queriedSubmissions"] == 0
    assert isinstance(coverage["queries"]["entries"], int)
    provider_summary = {item["id"]: item for item in coverage["providers"]}
    assert "naver" in provider_summary
    assert "google" in provider_summary
    assert "google_search" in provider_summary
    assert provider_summary["naver"]["queryCoverage"] == 0.0
    assert provider_summary["google"]["queryCoverage"] == 0.0
    assert bootstrap["coverageThresholds"] == {
        "coverageGoodRate": 70,
        "coverageWarnRate": 40,
        "queryGoodRate": 70,
        "queryWarnRate": 40,
    }


def test_sqlite_store_bootstrap_respects_coverage_threshold_env(tmp_path: Path, monkeypatch):
    monkeypatch.setenv("COPYRIGHTER_COVERAGE_GOOD_THRESHOLD", "85")
    monkeypatch.setenv("COPYRIGHTER_COVERAGE_WARN_THRESHOLD", "50")
    monkeypatch.setenv("COPYRIGHTER_QUERY_COVERAGE_GOOD_THRESHOLD", "90")
    monkeypatch.setenv("COPYRIGHTER_QUERY_COVERAGE_WARN_THRESHOLD", "30")

    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3")
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    thresholds = store.bootstrap()["coverageThresholds"]
    assert thresholds["coverageGoodRate"] == 85
    assert thresholds["coverageWarnRate"] == 50
    assert thresholds["queryGoodRate"] == 90
    assert thresholds["queryWarnRate"] == 30


def test_sqlite_store_uses_stable_digest_for_evidence_ids():
    evidence = Evidence(
        source=EvidenceSource.WEB_DETECTION,
        reason="Google full image match found",
        confidence=0.9,
        data={"url": "https://example.test/source", "rank": 1},
    )
    base = f"SUB-DB1:{evidence.source}:{evidence.reason}:{json.dumps(evidence.data, sort_keys=True, default=str)}"

    evidence_id = sqlite_store_module._evidence_id("SUB-DB1", evidence)

    assert evidence_id == "ev-" + hashlib.sha256(base.encode("utf-8")).hexdigest()[:24]


def test_fetch_url_bytes_uses_browser_image_headers_and_room_for_real_photos(monkeypatch):
    captured: dict[str, object] = {}

    class FakeResponse:
        def __enter__(self):
            return self

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

        def read(self, size=-1):
            captured["read_size"] = size
            return b"image-bytes"

    def fake_urlopen(request, timeout):
        captured["request"] = request
        captured["timeout"] = timeout
        return FakeResponse()

    monkeypatch.setattr(sqlite_store_module, "_open_url", fake_urlopen)
    monkeypatch.setattr(sqlite_store_module, "_assert_public_http_url", lambda url: None)

    content = sqlite_store_module._fetch_url_bytes("https://cdn.example.test/profile.webp")

    headers = {
        key.lower(): value
        for key, value in captured["request"].header_items()
    }
    assert content == b"image-bytes"
    assert captured["timeout"] == 10
    assert "mozilla/5.0" in headers["user-agent"].casefold()
    assert headers["accept"].startswith("image/avif,image/webp,image/apng,image/svg+xml,image/*")
    assert headers["accept-language"].startswith("ko-KR")
    assert headers["sec-fetch-dest"] == "image"
    assert captured["read_size"] >= 8_000_001


def test_sqlite_store_rejects_payloads_missing_required_shape(tmp_path: Path):
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3")
    store.initialize()

    with pytest.raises(ValueError, match="missing required fields"):
        store._put("evidence", "ev-bad", {"id": "ev-bad"})


def test_sqlite_store_schema_rejects_invalid_payload_json(tmp_path: Path):
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3")
    store.initialize()

    with store._connect() as conn:
        schema_version = conn.execute("pragma user_version").fetchone()[0]
        with pytest.raises(sqlite3.IntegrityError):
            conn.execute(
                """
                insert into providers (id, name, enabled, usage, quota, payload)
                values (?, ?, ?, ?, ?, ?)
                """,
                ("provider-bad", "Broken provider", 0, 0, 0, "not-json"),
            )

    assert schema_version >= 1


def test_sqlite_store_schema_enforces_typed_values_and_evidence_parent(
    tmp_path: Path,
):
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3")
    store.initialize()

    invalid_submission = {
        "id": "SUB-BAD",
        "title": "Bad risk band",
        "asset": "/media/bad.png",
        "riskScore": 0,
        "riskBand": "severe",
        "decisionStatus": "unreviewed",
        "providerState": {},
        "fileFacts": {},
        "evidence": [],
    }
    orphan_evidence = {
        "id": "ev-orphan",
        "group": "google",
        "source": "google",
        "title": "Orphan evidence",
        "confidence": 0.5,
        "query": "",
        "domain": "google",
        "url": "https://example.test/source",
        "retrievedAt": "2026-05-26 13:08:55",
        "contributed": True,
        "sourceEvidenceIds": [],
        "status": "active",
        "submission_id": "SUB-MISSING",
    }

    with store._connect() as conn:
        with pytest.raises(sqlite3.IntegrityError):
            conn.execute(
                """
                insert into submissions
                (id, title, risk_score, risk_band, decision_status, submitted_epoch, payload)
                values (?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    invalid_submission["id"],
                    invalid_submission["title"],
                    invalid_submission["riskScore"],
                    invalid_submission["riskBand"],
                    invalid_submission["decisionStatus"],
                    0,
                    json.dumps(invalid_submission),
                ),
            )
        with pytest.raises(sqlite3.IntegrityError):
            conn.execute(
                """
                insert into evidence
                (id, submission_id, source, confidence, status, contributed, payload)
                values (?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    orphan_evidence["id"],
                    orphan_evidence["submission_id"],
                    orphan_evidence["source"],
                    orphan_evidence["confidence"],
                    orphan_evidence["status"],
                    1,
                    json.dumps(orphan_evidence),
                ),
            )


def test_sqlite_store_migrates_legacy_tables_to_constrained_schema(tmp_path: Path):
    db_path = tmp_path / "copyrighter.sqlite3"
    submission = {
        "id": "SUB-LEGACY",
        "title": "Legacy row",
        "asset": "/media/legacy.png",
        "riskScore": 35,
        "riskBand": "medium",
        "decisionStatus": "unreviewed",
        "providerState": {},
        "fileFacts": {},
        "evidence": [],
    }
    evidence = {
        "id": "ev-legacy",
        "group": "google",
        "source": "google",
        "title": "Legacy evidence",
        "confidence": 0.5,
        "query": "",
        "domain": "google",
        "url": "https://example.test/source",
        "retrievedAt": "2026-05-26 13:08:55",
        "contributed": True,
        "sourceEvidenceIds": [],
        "status": "active",
        "submission_id": "SUB-LEGACY",
    }
    with sqlite3.connect(db_path) as conn:
        conn.executescript(
            """
            create table submissions (
                id text primary key,
                title text not null default '',
                risk_score integer not null default 0,
                risk_band text not null default 'low',
                decision_status text not null default 'unreviewed',
                submitted_epoch integer not null default 0,
                payload text not null
            );
            create table evidence (
                id text primary key,
                submission_id text not null,
                source text not null default '',
                confidence real not null default 0,
                status text not null default 'active',
                contributed integer not null default 1,
                payload text not null
            );
            """
        )
        conn.execute(
            """
            insert into submissions
            (id, title, risk_score, risk_band, decision_status, submitted_epoch, payload)
            values (?, ?, ?, ?, ?, ?, ?)
            """,
            (
                submission["id"],
                submission["title"],
                submission["riskScore"],
                submission["riskBand"],
                submission["decisionStatus"],
                0,
                json.dumps(submission),
            ),
        )
        conn.execute(
            """
            insert into evidence
            (id, submission_id, source, confidence, status, contributed, payload)
            values (?, ?, ?, ?, ?, ?, ?)
            """,
            (
                evidence["id"],
                evidence["submission_id"],
                evidence["source"],
                evidence["confidence"],
                evidence["status"],
                1,
                json.dumps(evidence),
            ),
        )

    store = CopyrighterStore(db_path)
    store.initialize()

    assert store.review("SUB-LEGACY")["title"] == "Legacy row"
    assert store.review("SUB-LEGACY")["evidence"][0]["id"] == "ev-legacy"
    with store._connect() as conn:
        with pytest.raises(sqlite3.IntegrityError):
            conn.execute(
                """
                insert into evidence
                (id, submission_id, source, confidence, status, contributed, payload)
                values (?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    "ev-orphan-after-migration",
                    "SUB-MISSING",
                    "google",
                    0.5,
                    "active",
                    1,
                    json.dumps({**evidence, "id": "ev-orphan-after-migration", "submission_id": "SUB-MISSING"}),
                ),
            )


def test_sqlite_store_populates_typed_columns_for_queryable_records(tmp_path: Path):
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    with store._connect() as conn:
        submission = conn.execute("select title, risk_score, risk_band from submissions where id = ?", ("SUB-DB1",)).fetchone()
        evidence = conn.execute("select source, confidence, status from evidence limit 1").fetchone()

    assert submission["title"] == "DB sample"
    assert submission["risk_score"] >= 0
    assert submission["risk_band"] in {"low", "medium", "high", "failed"}
    assert evidence["source"]
    assert evidence["confidence"] >= 0
    assert evidence["status"]


def test_sqlite_store_updates_submission_without_dropping_child_evidence(tmp_path: Path):
    db_path = tmp_path / "copyrighter.sqlite3"
    image_store = _image_store(tmp_path / "submissions")
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(image_store)
    submission = store.review("SUB-DB1")

    with store._connect() as conn:
        before_count = conn.execute(
            "select count(*) from evidence where submission_id = ?",
            ("SUB-DB1",),
        ).fetchone()[0]

    submission["title"] = "Updated title"
    store._put("submissions", "SUB-DB1", submission)

    with store._connect() as conn:
        after_count = conn.execute(
            "select count(*) from evidence where submission_id = ?",
            ("SUB-DB1",),
        ).fetchone()[0]

    assert before_count > 0
    assert after_count == before_count


def test_sqlite_store_persists_decision_and_provider_state(tmp_path: Path):
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    store.record_decision("SUB-DB1", "rejected", "copyright concern")
    store.set_provider_enabled("naver", False)

    reopened = CopyrighterStore(db_path)
    reopened.initialize()
    bootstrap = reopened.bootstrap()
    submission = bootstrap["submissions"][0]
    naver = next(provider for provider in bootstrap["providers"] if provider["id"] == "naver")

    assert submission["decisionStatus"] == "rejected"
    assert naver["enabled"] is False
    assert any(event["event"] == "Operator decision created" for event in bootstrap["auditEvents"])


def test_sqlite_store_rejects_enabling_provider_without_runtime_connection(tmp_path: Path):
    image_store = _image_store(tmp_path / "submissions")
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(
        db_path,
        provider_runtime=build_provider_runtime(env={}, transport=FakeJsonTransport({})),
    )
    store.initialize()
    store.seed_from_image_store(image_store)

    with pytest.raises(ValueError, match="missing GOOGLE_CUSTOM_SEARCH_API_KEY or GOOGLE_CUSTOM_SEARCH_CX"):
        store.set_provider_enabled("google_search", True)

    google_search = next(provider for provider in store.providers() if provider["id"] == "google_search")
    submission = store.bootstrap()["submissions"][0]
    assert google_search["enabled"] is False
    assert submission["providerState"]["google_search"] == "disabled"


def test_sqlite_store_allows_reenabling_configured_provider(tmp_path: Path):
    image_store = _image_store(tmp_path / "submissions")
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
        },
        transport=FakeJsonTransport({}),
    )
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()
    store.seed_from_image_store(image_store)
    store.set_provider_enabled("google_search", False)

    providers = store.set_provider_enabled("google_search", True)
    google_search = next(provider for provider in providers if provider["id"] == "google_search")
    submission = store.bootstrap()["submissions"][0]

    assert google_search["enabled"] is True
    assert submission["providerState"]["google_search"] == "not_run"


def test_sqlite_store_marks_evidence_status_without_creating_watchlist_candidate(tmp_path: Path):
    image_store = _image_store(tmp_path / "submissions")
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(image_store)
    face_evidence = next(item for item in store.review("SUB-DB1")["evidence"] if item["source"] == "face")

    review = store.mark_evidence_status("SUB-DB1", face_evidence["id"], "false_positive", "얼굴 감지는 무관")
    updated = next(item for item in review["evidence"] if item["id"] == face_evidence["id"])

    assert updated["operatorStatus"] == "false_positive"
    assert updated["operatorStatusLabel"] == "오탐"
    assert updated["contributed"] is False
    assert review["riskScore"] == 0
    assert review["riskBand"] == "low"
    assert store.bootstrap()["knowledgeEntries"] == []


def test_sqlite_store_held_decision_creates_watchlist_candidate_from_submission_image(tmp_path: Path):
    image_store = _image_store(tmp_path / "submissions")
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(image_store)
    face_evidence = next(item for item in store.review("SUB-DB1")["evidence"] if item["source"] == "face")
    store.mark_evidence_status("SUB-DB1", face_evidence["id"], "used_for_judgment", "판정 근거")

    store.record_decision("SUB-DB1", "held", "권리 확인 필요", image_store=image_store)
    entry = store.bootstrap()["knowledgeEntries"][0]

    assert entry["entryStatus"] == "watchlist"
    assert entry["originDecisionStatus"] == "held"
    assert entry["sourceSubmissionId"] == "SUB-DB1"
    assert entry["sourceEvidenceIds"] == [face_evidence["id"]]
    assert entry["sampleFingerprints"]
    assert entry["contributionCount"] == 0


def test_sqlite_store_approved_decision_does_not_create_watchlist_candidate(tmp_path: Path):
    image_store = _image_store(tmp_path / "submissions")
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(image_store)

    store.record_decision("SUB-DB1", "approved", "사용 허가 확인", image_store=image_store)

    assert store.bootstrap()["knowledgeEntries"] == []


def test_sqlite_store_watchlist_similarity_is_high_risk_but_separate_from_confirmed_db(tmp_path: Path):
    image_root = tmp_path / "submissions"
    image_store = _image_store(image_root)
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(image_store)
    store.record_decision("SUB-DB1", "held", "권리 확인 필요", image_store=image_store)

    (image_root / "images" / "copy.svg").write_text(
        "<svg><!-- FACE PERSON --></svg>",
        encoding="utf-8",
    )
    (image_root / "submissions.json").write_text(
        """
        [
          {
            "id": "SUB-DB1",
            "title": "DB sample",
            "file": "images/sample.svg",
            "width": 800,
            "height": 600,
            "submitted_at": "2026-05-26 10:30"
          },
          {
            "id": "SUB-DB2",
            "title": "Copied DB sample",
            "file": "images/copy.svg",
            "width": 800,
            "height": 600,
            "submitted_at": "2026-05-26 10:40"
          }
        ]
        """,
        encoding="utf-8",
    )

    store.seed_from_image_store(LocalSubmissionImageStore(image_root))
    review = store.review("SUB-DB2")
    watchlist_evidence = [
        item for item in review["evidence"] if item.get("knowledgeEntryStatus") == "watchlist"
    ]
    watchlist_entry = next(item for item in store.bootstrap()["knowledgeEntries"] if item["entryStatus"] == "watchlist")

    assert review["riskBand"] == "high"
    assert watchlist_evidence
    assert watchlist_evidence[0]["group"] == "watchlist"
    assert "주의 후보 이미지 유사도" in watchlist_evidence[0]["title"]
    assert watchlist_entry["contributionCount"] == 1


def test_sqlite_store_promotes_and_excludes_watchlist_candidates(tmp_path: Path):
    image_store = _image_store(tmp_path / "submissions")
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(image_store)
    store.record_decision("SUB-DB1", "rejected", "권리 침해 우려", image_store=image_store)
    entry_id = store.bootstrap()["knowledgeEntries"][0]["id"]

    promoted = store.promote_watchlist_entry(entry_id)
    promoted_entry = next(item for item in promoted["knowledgeEntries"] if item["id"] == entry_id)
    excluded = store.exclude_watchlist_entry(entry_id, "오탐 확인")
    excluded_entry = next(item for item in excluded["knowledgeEntries"] if item["id"] == entry_id)

    assert promoted_entry["entryStatus"] == "confirmed"
    assert promoted_entry["active"] is True
    assert excluded_entry["entryStatus"] == "excluded"
    assert excluded_entry["active"] is False
    assert excluded_entry["excludedReason"] == "오탐 확인"


def test_sqlite_store_imports_new_manifest_records_without_resetting_existing_state(tmp_path: Path):
    image_root = tmp_path / "submissions"
    image_store = _image_store(image_root)
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(image_store)
    store.set_provider_enabled("naver", False)

    (image_root / "images" / "second.svg").write_text("<svg></svg>", encoding="utf-8")
    (image_root / "submissions.json").write_text(
        """
        [
          {
            "id": "SUB-DB1",
            "title": "DB sample",
            "file": "images/sample.svg",
            "width": 800,
            "height": 600,
            "submitted_at": "2026-05-26 10:30"
          },
          {
            "id": "SUB-DB2",
            "title": "Second DB sample",
            "file": "images/second.svg",
            "width": 400,
            "height": 300,
            "submitted_at": "2026-05-26 10:40"
          }
        ]
        """,
        encoding="utf-8",
    )

    store.seed_from_image_store(LocalSubmissionImageStore(image_root))
    bootstrap = store.bootstrap()
    provider = next(item for item in bootstrap["providers"] if item["id"] == "naver")

    assert [item["id"] for item in bootstrap["submissions"]] == ["SUB-DB1", "SUB-DB2"]
    assert provider["enabled"] is False


def test_sqlite_store_returns_new_import_count_when_reloading_submission_store(tmp_path: Path):
    image_root = tmp_path / "submissions"
    image_store = _image_store(image_root)
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()

    first_count = store.seed_from_image_store(image_store)
    (image_root / "images" / "copied.svg").write_text("<svg></svg>", encoding="utf-8")
    second_count = store.seed_from_image_store(LocalSubmissionImageStore(image_root))

    assert first_count == 1
    assert second_count == 1
    assert [item["id"] for item in store.bootstrap()["submissions"]] == ["SUB-DB1", "copied"]


def test_sqlite_store_switches_active_queue_when_importing_from_different_folders(tmp_path: Path):
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()

    queue_a = tmp_path / "queue-a"
    queue_a_images = queue_a / "images"
    queue_a_images.mkdir(parents=True)
    (queue_a_images / "alpha.svg").write_text("<svg><!-- A --></svg>", encoding="utf-8")
    (queue_a / "submissions.json").write_text(
        """
        [
          {
            "id": "SUB-QUEUE-A",
            "title": "Queue A",
            "file": "images/alpha.svg",
            "width": 640,
            "height": 480,
            "submitted_at": "2026-05-26 10:00"
          }
        ]
        """,
        encoding="utf-8",
    )

    queue_b = tmp_path / "queue-b"
    queue_b_images = queue_b / "images"
    queue_b_images.mkdir(parents=True)
    (queue_b_images / "beta.svg").write_text("<svg><!-- B --></svg>", encoding="utf-8")
    (queue_b / "submissions.json").write_text(
        """
        [
          {
            "id": "SUB-QUEUE-B",
            "title": "Queue B",
            "file": "images/beta.svg",
            "width": 640,
            "height": 480,
            "submitted_at": "2026-05-26 10:10"
          }
        ]
        """,
        encoding="utf-8",
    )

    queue_a_payload = store.seed_from_image_store(LocalSubmissionImageStore(queue_a))
    bootstrap_a = store.bootstrap()
    queue_b_payload = store.seed_from_image_store(LocalSubmissionImageStore(queue_b))
    bootstrap_b = store.bootstrap()
    queue_a_again = store.seed_from_image_store(LocalSubmissionImageStore(queue_a))
    bootstrap_a_again = store.bootstrap()

    assert queue_a_payload == 1
    assert queue_b_payload == 1
    assert queue_a_again == 0
    assert bootstrap_a["submissionQueue"]["folderPath"] == str(queue_a.resolve())
    assert [item["id"] for item in bootstrap_a["submissions"]] == ["SUB-QUEUE-A"]
    assert bootstrap_b["submissionQueue"]["folderPath"] == str(queue_b.resolve())
    assert [item["id"] for item in bootstrap_b["submissions"]] == ["SUB-QUEUE-B"]
    assert bootstrap_a_again["submissionQueue"]["folderPath"] == str(queue_a.resolve())
    assert [item["id"] for item in bootstrap_a_again["submissions"]] == ["SUB-QUEUE-A"]


def test_sqlite_store_persists_active_queue_in_database(tmp_path: Path):
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()

    folder_a = tmp_path / "queue-persist-a"
    folder_a_images = folder_a / "images"
    folder_a_images.mkdir(parents=True)
    (folder_a_images / "persist.svg").write_text("<svg><!-- PERSIST --></svg>", encoding="utf-8")
    (folder_a / "submissions.json").write_text(
        """
        [
          {
            "id": "SUB-PERSIST",
            "title": "Persistent Queue",
            "file": "images/persist.svg",
            "width": 640,
            "height": 480,
            "submitted_at": "2026-05-26 10:20"
          }
        ]
        """,
        encoding="utf-8",
    )

    seed_count = store.seed_from_image_store(LocalSubmissionImageStore(folder_a))

    assert seed_count == 1
    bootstrap_before = store.bootstrap()
    assert bootstrap_before["submissionQueue"]["folderPath"] == str(folder_a.resolve())
    assert [item["id"] for item in bootstrap_before["submissions"]] == ["SUB-PERSIST"]

    reopened = CopyrighterStore(db_path)
    reopened.initialize()
    bootstrap_after = reopened.bootstrap()
    assert bootstrap_after["submissionQueue"]["folderPath"] == str(folder_a.resolve())
    assert [item["id"] for item in bootstrap_after["submissions"]] == ["SUB-PERSIST"]


def test_sqlite_store_reload_uses_remaining_files_only_for_the_active_queue(tmp_path: Path):
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()

    folder = tmp_path / "active-folder"
    images = folder / "images"
    images.mkdir(parents=True)
    (images / "keep.svg").write_text("<svg><!-- KEEP --></svg>", encoding="utf-8")
    (images / "drop.svg").write_text("<svg><!-- DROP --></svg>", encoding="utf-8")
    (folder / "submissions.json").write_text(
        """
        [
          {
            "id": "SUB-KEEP",
            "title": "Keep Submission",
            "file": "images/keep.svg",
            "width": 640,
            "height": 480,
            "submitted_at": "2026-05-26 10:00"
          },
          {
            "id": "SUB-DROP",
            "title": "Drop Submission",
            "file": "images/drop.svg",
            "width": 640,
            "height": 480,
            "submitted_at": "2026-05-26 10:01"
          }
        ]
        """,
        encoding="utf-8",
    )

    first_import = store.seed_from_image_store(LocalSubmissionImageStore(folder))
    assert first_import == 2
    assert [item["id"] for item in store.bootstrap()["submissions"]] == ["SUB-DROP", "SUB-KEEP"]

    (images / "drop.svg").unlink()
    second_import = store.seed_from_image_store(LocalSubmissionImageStore(folder))
    bootstrap = store.bootstrap()

    assert second_import == 0
    assert [item["id"] for item in bootstrap["submissions"]] == ["SUB-KEEP"]


def test_sqlite_store_prunes_submission_records_when_source_file_is_deleted(tmp_path: Path):
    image_root = tmp_path / "submissions"
    image_store = _image_store(image_root)
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(image_store)

    (image_root / "images" / "sample.svg").unlink()
    store.seed_from_image_store(LocalSubmissionImageStore(image_root))

    assert store.bootstrap()["submissions"] == []
    with sqlite3.connect(db_path) as conn:
        assert conn.execute("select count(*) from evidence").fetchone()[0] == 0


def test_sqlite_store_refreshes_existing_local_face_evidence_when_reloaded(tmp_path: Path, monkeypatch):
    class NoFaceDetector:
        def detect(self, image):
            return FacePersonSignal(face_count=0, person_count=0)

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

    image_store = _image_store(tmp_path / "submissions")
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()

    monkeypatch.setattr(sqlite_store_module, "HeuristicFacePersonDetector", lambda: NoFaceDetector())
    store.seed_from_image_store(image_store)
    assert not any(item["source"] == "face" for item in store.review("SUB-DB1")["evidence"])

    monkeypatch.setattr(sqlite_store_module, "HeuristicFacePersonDetector", lambda: OneFaceDetector())
    imported = store.seed_from_image_store(image_store)
    review = store.review("SUB-DB1")

    assert imported == 0
    assert any(item["source"] == "face" for item in review["evidence"])
    assert review["riskBand"] == "medium"


def test_sqlite_store_rescore_ignores_non_contributing_queued_rerun_marker(tmp_path: Path):
    image_store = _image_store(tmp_path / "submissions")
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(image_store)
    store._put(
        "evidence",
        "ev-queued-marker",
        {
            "id": "ev-queued-marker",
            "group": "internal",
            "source": "fingerprint",
            "title": "재분석 요청이 접수됨",
            "confidence": 0,
            "query": "",
            "domain": "internal",
            "url": "",
            "retrievedAt": "2026-05-26 13:08:55",
            "contributed": False,
            "sourceEvidenceIds": [],
            "status": "queued",
            "submission_id": "SUB-DB1",
        },
    )

    store._rescore_submission("SUB-DB1")
    review = store.review("SUB-DB1")

    assert review["riskScore"] == 35
    assert review["riskBand"] == "medium"
    assert "Face/person detected" in review["reasons"]
    assert "재분석 요청이 접수됨" not in review["reasons"]


def test_sqlite_store_seed_rescores_existing_saved_search_evidence(tmp_path: Path):
    image_store = _image_store(tmp_path / "submissions")
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(image_store)
    submission = store.review("SUB-DB1")
    submission["riskScore"] = 100
    submission["riskBand"] = "high"
    store._put("submissions", "SUB-DB1", submission)
    for index in range(10):
        store._put(
            "evidence",
            f"ev-google-visual-{index}",
            {
                "id": f"ev-google-visual-{index}",
                "group": "google",
                "source": "google",
                "title": "Google visually similar image found",
                "confidence": 0.55,
                "query": "",
                "domain": "google",
                "url": f"https://example.test/{index}.jpg",
                "imageUrl": f"https://example.test/{index}.jpg",
                "thumbnailUrl": "",
                "pageTitle": "",
                "matchType": "visual",
                "rank": "",
                "providerScore": 0,
                "retrievedAt": "2026-05-26 13:08:55",
                "contributed": True,
                "sourceEvidenceIds": [],
                "status": "rerun",
                "submission_id": "SUB-DB1",
            },
        )

    reopened = CopyrighterStore(db_path)
    reopened.initialize()
    reopened.seed_from_image_store(image_store)
    review = reopened.review("SUB-DB1")

    assert review["riskBand"] == "medium"
    assert review["riskScore"] < 70


def test_sqlite_store_refreshes_existing_file_facts_from_image_store(tmp_path: Path):
    image_root = tmp_path / "submissions"
    image_dir = image_root / "images"
    image_dir.mkdir(parents=True)
    (image_dir / "sample.jpg").write_bytes(b"jpg-bytes")
    image_store = LocalSubmissionImageStore(image_root)
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(image_store)
    submission = store.review("sample")
    submission["fileFacts"]["size"] = "1 x 1"
    submission["fileFacts"]["format"] = "FILE"
    store._put("submissions", "sample", submission)

    (image_root / "submissions.json").write_text(
        """
        [
          {
            "id": "sample",
            "title": "sample",
            "file": "images/sample.jpg",
            "width": 300,
            "height": 200
          }
        ]
        """,
        encoding="utf-8",
    )
    store.seed_from_image_store(LocalSubmissionImageStore(image_root))
    review = store.review("sample")

    assert review["fileFacts"]["size"] == "300 x 200"
    assert review["fileFacts"]["format"] == "JPG"


def test_sqlite_store_syncs_provider_connection_state_from_env_runtime(tmp_path: Path):
    runtime = build_provider_runtime(
        env={
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
            "OLLAMA_MODEL": "qwen2.5:0.5b-instruct",
        },
        transport=FakeJsonTransport({"items": []}),
    )
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    providers = {item["id"]: item for item in store.providers()}

    assert providers["naver"]["enabled"] is True
    assert providers["google"]["enabled"] is True
    assert providers["llm"]["enabled"] is True
    assert providers["naver"]["lastFailure"] == "없음"


def test_sqlite_store_syncs_existing_submission_provider_state_after_env_change(tmp_path: Path):
    image_store = _image_store(tmp_path / "submissions")
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path, provider_runtime=build_provider_runtime(env={}, transport=FakeJsonTransport({})))
    store.initialize()
    store.seed_from_image_store(image_store)

    runtime = build_provider_runtime(
        env={
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
            "OLLAMA_MODEL": "qwen2.5:0.5b-instruct",
        },
        transport=FakeJsonTransport({"items": []}),
    )
    reopened = CopyrighterStore(db_path, provider_runtime=runtime)
    reopened.initialize()
    reopened.seed_from_image_store(image_store)

    submission = reopened.bootstrap()["submissions"][0]

    assert submission["providerState"]["naver"] == "not_run"
    assert submission["providerState"]["google"] == "not_run"
    assert submission["providerState"]["google_search"] == "not_run"
    assert submission["providerState"]["llm"] == "covered"


def test_sqlite_store_emergency_disable_marks_google_custom_search_state_disabled(tmp_path: Path):
    runtime = build_provider_runtime(
        env={
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
            "OLLAMA_MODEL": "qwen2.5:0.5b-instruct",
        },
        transport=FakeJsonTransport({"items": []}),
    )
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))
    submission = store.bootstrap()["submissions"][0]
    del submission["providerState"]["google_search"]
    store._put("submissions", submission["id"], submission)

    store.emergency_disable_external_providers()
    submission = store.bootstrap()["submissions"][0]

    assert submission["providerState"]["naver"] == "disabled"
    assert submission["providerState"]["google"] == "disabled"
    assert submission["providerState"]["google_search"] == "disabled"
    assert submission["providerState"]["llm"] == "disabled"


def test_sqlite_store_manual_naver_search_uses_real_env_client(tmp_path: Path):
    transport = FakeJsonTransport(
        {
            "items": [
                {
                    "title": "IU official album cover",
                    "link": "https://example.test/image.jpg",
                    "thumbnail": "https://example.test/thumb.jpg",
                    "sizeheight": "900",
                    "sizewidth": "900",
                    "page_url": "https://example.test/page",
                }
            ]
        }
    )
    runtime = build_provider_runtime(
        env={"NAVER_CLIENT_ID": "naver-id", "NAVER_CLIENT_SECRET": "naver-secret"},
        transport=transport,
    )
    runtime.llm_assistant = None
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    review = store.manual_search("SUB-DB1", "naver", "IU album cover")

    assert "openapi.naver.com/v1/search/image" in transport.calls[0]["url"]
    assert review["queryHistory"][0]["query"] == "IU album cover"
    assert any(item["source"] == "naver" and item["url"] == "https://example.test/page" for item in review["evidence"])


def test_sqlite_store_manual_naver_search_falls_back_to_blog_page_images_when_image_search_is_empty(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={"responses": [{"webDetection": {}}]},
        naver_response={"items": []},
        naver_blog_response={
            "items": [
                {
                    "title": "IU official profile blog",
                    "description": "profile image",
                    "link": "https://example.test/manual-blog-post",
                    "bloggername": "official blog",
                    "bloggerlink": "https://blog.example.test",
                    "postdate": "20260527",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={"NAVER_CLIENT_ID": "naver-id", "NAVER_CLIENT_SECRET": "naver-secret"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content

    def fetcher(url: str) -> bytes:
        if url == "https://example.test/manual-blog-post":
            return b'<meta property="og:image" content="https://example.test/manual-blog-image.png">'
        if url == "https://example.test/manual-blog-image.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(image_store)

    review = store.manual_search("SUB-PNG1", "naver", "IU official profile", image_store)
    naver_calls = [call["url"] for call in transport.calls if "openapi.naver.com/v1/search" in call["url"]]

    assert any("/v1/search/image" in url for url in naver_calls)
    assert any("/v1/search/blog" in url for url in naver_calls)
    assert not any("/v1/search/webkr" in url for url in naver_calls)
    assert review["queryHistory"][0]["provider"] == "naver"
    assert review["queryHistory"][0]["status"] == "manual"
    assert any(
        item["source"] == "naver"
        and item["url"] == "https://example.test/manual-blog-post"
        and item["searchType"] == "blog"
        and item["matchType"] == "page"
        for item in review["evidence"]
    )
    assert any(
        item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
        and item["remoteImageUrl"] == "https://example.test/manual-blog-image.png"
        for item in review["evidence"]
    )
    assert review["riskBand"] == "high"


def test_sqlite_store_manual_naver_search_falls_back_to_web_page_images_when_image_and_blog_are_empty(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={"responses": [{"webDetection": {}}]},
        naver_response={"items": []},
        naver_blog_response={"items": []},
        naver_web_response={
            "items": [
                {
                    "title": "IU official profile web page",
                    "description": "profile image",
                    "link": "https://example.test/manual-web-profile",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={"NAVER_CLIENT_ID": "naver-id", "NAVER_CLIENT_SECRET": "naver-secret"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content

    def fetcher(url: str) -> bytes:
        if url == "https://example.test/manual-web-profile":
            return b'<meta property="og:image" content="https://example.test/manual-web-image.png">'
        if url == "https://example.test/manual-web-image.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(image_store)

    review = store.manual_search("SUB-PNG1", "naver", "IU official profile", image_store)
    naver_calls = [call["url"] for call in transport.calls if "openapi.naver.com/v1/search" in call["url"]]

    assert any("/v1/search/image" in url for url in naver_calls)
    assert any("/v1/search/blog" in url for url in naver_calls)
    assert any("/v1/search/webkr" in url for url in naver_calls)
    assert review["queryHistory"][0]["provider"] == "naver"
    assert review["queryHistory"][0]["status"] == "manual"
    assert any(
        item["source"] == "naver"
        and item["url"] == "https://example.test/manual-web-profile"
        and item["searchType"] == "web"
        and item["matchType"] == "page"
        for item in review["evidence"]
    )
    assert any(
        item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
        and item["remoteImageUrl"] == "https://example.test/manual-web-image.png"
        for item in review["evidence"]
    )
    assert review["riskBand"] == "high"


def test_sqlite_store_manual_google_custom_search_compares_result_image(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={"responses": [{"webDetection": {}}]},
        naver_response={"items": []},
        google_custom_image_response={
            "items": [
                {
                    "title": "IU official image",
                    "link": "https://example.test/manual-google-iu.png",
                    "displayLink": "example.test",
                    "snippet": "official profile",
                    "image": {
                        "thumbnailLink": "https://example.test/manual-google-iu-thumb.png",
                        "contextLink": "https://example.test/manual-google-profile",
                    },
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=lambda url: submitted_image,
    )
    store.initialize()
    store.seed_from_image_store(image_store)

    review = store.manual_search("SUB-PNG1", "google_search", "IU official profile", image_store)
    custom_calls = [call["url"] for call in transport.calls if "www.googleapis.com/customsearch/v1" in call["url"]]

    assert len(custom_calls) == 1
    assert "q=IU+official+profile" in custom_calls[0]
    assert "searchType=image" in custom_calls[0]
    assert review["queryHistory"][0]["provider"] == "google_search"
    assert review["queryHistory"][0]["status"] == "manual"
    assert any(
        item["source"] == "google"
        and item["domain"] == "google_custom_search"
        and item["searchType"] == "image"
        and item["url"] == "https://example.test/manual-google-profile"
        for item in review["evidence"]
    )
    assert any(
        item["source"] == "fingerprint"
        and item["matchType"] == "search_result_image"
        and item["remoteImageUrl"] == "https://example.test/manual-google-iu.png"
        for item in review["evidence"]
    )
    assert review["riskBand"] == "high"


def test_sqlite_store_manual_knowledge_image_participates_in_similarity_rerun(tmp_path: Path):
    image_store = _image_store(tmp_path / "submissions")
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(image_store)
    sample_content = image_store.image_payload("SUB-DB1").content

    entry = store.register_manual_knowledge_entry(
        {
            "name": "Known rejected reference",
            "type": "rejected_reference",
            "aliases": ["known sample"],
            "keywords": ["manual reference"],
            "memo": "Reject unless rights evidence is supplied.",
            "image": {
                "filename": "known.svg",
                "content_type": "image/svg+xml",
                "data": base64.b64encode(sample_content).decode("ascii"),
            },
        }
    )
    review = store.rerun_enrichment("SUB-DB1", image_store)

    assert entry["sampleFingerprints"]
    assert entry["imageAsset"].startswith("/knowledge-media/")
    assert any(
        item["source"] == "fingerprint"
        and item["status"] == "rerun"
        and "Knowledge base image similarity" in item["title"]
        for item in review["evidence"]
    )
    assert any(item["asset"] == entry["imageAsset"] for item in review["similar"])


def test_sqlite_store_collects_keyword_candidates_and_promotes_one_to_knowledge(tmp_path: Path):
    transport = FakeJsonTransport(
        {
            "items": [
                {
                    "title": "<b>Winter</b> official image",
                    "link": "https://example.test/winter.jpg",
                    "thumbnail": "https://example.test/winter-thumb.jpg",
                    "page_url": "https://example.test/winter-page",
                    "sizeheight": "900",
                    "sizewidth": "600",
                }
            ]
        }
    )
    runtime = build_provider_runtime(
        env={"NAVER_CLIENT_ID": "naver-id", "NAVER_CLIENT_SECRET": "naver-secret"},
        transport=transport,
    )
    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=lambda url: _candidate_png_bytes((180, 190, 210) if "profile" in url else (80, 120, 170)),
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("winter aespa")
    candidate = collection["collectionCandidates"][0]
    promoted = store.promote_collection_candidate(
        candidate["id"],
        {"memo": "Promoted after operator review."},
    )

    assert collection["collected"] == 1
    assert candidate["query"] == "winter aespa"
    assert candidate["title"] == "Winter official image"
    assert candidate["imageAsset"].startswith("/collected-media/")
    assert candidate["sampleFingerprints"]
    assert promoted["collectionCandidates"][0]["status"] == "promoted"
    assert promoted["knowledgeEntries"][0]["sampleFingerprints"] == candidate["sampleFingerprints"]


def test_sqlite_store_reuses_collected_candidate_images_for_repeated_keyword_search(tmp_path: Path):
    transport = FakeJsonTransport(
        {
            "items": [
                {
                    "title": "<b>Winter</b> official image",
                    "link": "https://example.test/winter.jpg",
                    "thumbnail": "https://example.test/winter-thumb.jpg",
                    "page_url": "https://example.test/winter-page",
                    "sizeheight": "900",
                    "sizewidth": "600",
                }
            ]
        }
    )
    runtime = build_provider_runtime(
        env={"NAVER_CLIENT_ID": "naver-id", "NAVER_CLIENT_SECRET": "naver-secret"},
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/winter.jpg":
            return _candidate_png_bytes()
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    first = store.collect_keyword_candidates("winter aespa")
    second = store.collect_keyword_candidates("winter aespa")

    assert fetched_urls == ["https://example.test/winter.jpg"]
    assert first["collectionCandidates"][0]["imageAsset"] == second["collectionCandidates"][0]["imageAsset"]
    assert first["collectionCandidates"][0]["sampleFingerprints"] == second["collectionCandidates"][0]["sampleFingerprints"]


def test_sqlite_store_skips_keyword_candidates_without_comparable_image_fingerprint(tmp_path: Path):
    transport = FakeJsonTransport(
        {
            "items": [
                {
                    "title": "Winter official image",
                    "link": "https://example.test/winter.jpg",
                    "thumbnail": "https://example.test/winter-thumb.jpg",
                    "page_url": "https://example.test/winter-page",
                }
            ]
        }
    )
    runtime = build_provider_runtime(
        env={"NAVER_CLIENT_ID": "naver-id", "NAVER_CLIENT_SECRET": "naver-secret"},
        transport=transport,
    )
    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=lambda url: b"<html>not an image</html>",
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("winter aespa")

    assert collection["collected"] == 0
    assert collection["collectionCandidates"] == []
    assert not store.collection_image_dir.exists()


def test_sqlite_store_collects_unwrapped_search_result_image_candidates(tmp_path: Path):
    transport = FakeJsonTransport(
        {
            "items": [
                {
                    "title": "Winter official image",
                    "link": "https://search.example.test/proxy?imgurl=https%3A%2F%2Fcdn.example.test%2Fwinter-official.png",
                    "thumbnail": "https://search.example.test/thumb?imgurl=https%3A%2F%2Fcdn.example.test%2Fwinter-thumb.png",
                    "page_url": "https://example.test/winter-page",
                }
            ]
        }
    )
    runtime = build_provider_runtime(
        env={"NAVER_CLIENT_ID": "naver-id", "NAVER_CLIENT_SECRET": "naver-secret"},
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://cdn.example.test/winter-official.png":
            return _candidate_png_bytes()
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("winter aespa")
    candidate = collection["collectionCandidates"][0]

    assert fetched_urls == ["https://cdn.example.test/winter-official.png"]
    assert collection["collected"] == 1
    assert candidate["imageUrl"] == "https://cdn.example.test/winter-official.png"
    assert candidate["thumbnailUrl"] == "https://cdn.example.test/winter-thumb.png"
    assert candidate["sourceUrl"] == "https://example.test/winter-page"
    assert candidate["sourceCandidateType"] == "search_result_image"
    assert candidate["sampleFingerprints"]


def test_sqlite_store_collects_search_result_thumbnail_when_original_image_fails(tmp_path: Path):
    transport = FakeJsonTransport(
        {
            "items": [
                {
                    "title": "Winter official image",
                    "link": "https://example.test/winter-original.png",
                    "thumbnail": "https://example.test/winter-thumb.png",
                    "page_url": "https://example.test/winter-page",
                }
            ]
        }
    )
    runtime = build_provider_runtime(
        env={"NAVER_CLIENT_ID": "naver-id", "NAVER_CLIENT_SECRET": "naver-secret"},
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/winter-thumb.png":
            return _candidate_png_bytes()
        raise ValueError(f"image unavailable: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("winter aespa")
    candidate = collection["collectionCandidates"][0]

    assert fetched_urls == [
        "https://example.test/winter-original.png",
        "https://example.test/winter-thumb.png",
    ]
    assert collection["collected"] == 1
    assert candidate["imageUrl"] == "https://example.test/winter-thumb.png"
    assert candidate["thumbnailUrl"] == "https://example.test/winter-thumb.png"
    assert candidate["sourceCandidateType"] == "search_result_image"
    assert candidate["sampleFingerprints"]


def test_sqlite_store_collects_google_custom_keyword_candidates(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={"items": []},
        google_custom_image_response={
            "items": [
                {
                    "title": "<b>IU</b> official profile",
                    "link": "https://example.test/iu-profile.png",
                    "displayLink": "example.test",
                    "snippet": "official profile image",
                    "image": {
                        "thumbnailLink": "https://example.test/iu-thumb.png",
                        "contextLink": "https://example.test/iu-profile-page",
                    },
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
        },
        transport=transport,
    )
    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=lambda url: _candidate_png_bytes(
            marker="left" if "profile" in url else "right"
        ),
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("IU official profile", "google_search")
    candidate = collection["collectionCandidates"][0]
    google_provider = next(item for item in collection["providers"] if item["id"] == "google_search")

    assert collection["collected"] == 1
    assert candidate["provider"] == "google_search"
    assert candidate["query"] == "IU official profile"
    assert candidate["title"] == "IU official profile"
    assert candidate["imageUrl"] == "https://example.test/iu-profile.png"
    assert candidate["thumbnailUrl"] == "https://example.test/iu-thumb.png"
    assert candidate["sourceUrl"] == "https://example.test/iu-profile-page"
    assert candidate["imageAsset"].startswith("/collected-media/")
    assert candidate["sampleFingerprints"]
    assert google_provider["usage"] == 1


def test_sqlite_store_collects_google_custom_image_pagemap_candidate_when_primary_image_fails(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={"items": []},
        google_custom_image_response={
            "items": [
                {
                    "title": "<b>IU</b> pagemap profile",
                    "link": "https://example.test/blocked-original.png",
                    "displayLink": "example.test",
                    "snippet": "official profile image",
                    "image": {
                        "thumbnailLink": "https://example.test/blocked-thumb.png",
                        "contextLink": "https://example.test/iu-profile-page",
                    },
                    "pagemap": {
                        "cse_image": [{"src": "/media/iu-pagemap-profile.png"}],
                    },
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
        },
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/media/iu-pagemap-profile.png":
            return _candidate_png_bytes(marker="left")
        raise ValueError(f"image unavailable: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("IU official profile", "google_search")
    candidate = collection["collectionCandidates"][0]

    assert fetched_urls == [
        "https://example.test/blocked-original.png",
        "https://example.test/blocked-thumb.png",
        "https://example.test/media/iu-pagemap-profile.png",
    ]
    assert collection["collected"] == 1
    assert candidate["imageUrl"] == "https://example.test/media/iu-pagemap-profile.png"
    assert candidate["sourceUrl"] == "https://example.test/iu-profile-page"
    assert candidate["sourceCandidateType"] == "provider_page_image"
    assert candidate["sampleFingerprints"]


def test_sqlite_store_collects_google_custom_web_page_image_candidates_when_image_search_is_empty(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={"items": []},
        google_custom_image_response={"items": []},
        google_custom_web_response={
            "items": [
                {
                    "title": "<b>IU</b> official page",
                    "link": "https://example.test/iu-profile-page",
                    "displayLink": "example.test",
                    "snippet": "official profile page",
                    "pagemap": {
                        "cse_image": [{"src": "https://example.test/iu-page-image.png"}],
                    },
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
        },
        transport=transport,
    )
    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=lambda url: _candidate_png_bytes(
            marker="left" if "profile" in url else "right"
        ),
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("IU official profile", "google_search")
    candidate = collection["collectionCandidates"][0]
    google_provider = next(item for item in collection["providers"] if item["id"] == "google_search")

    assert collection["collected"] == 1
    assert candidate["provider"] == "google_search"
    assert candidate["imageUrl"] == "https://example.test/iu-page-image.png"
    assert candidate["thumbnailUrl"] == "https://example.test/iu-page-image.png"
    assert candidate["sourceUrl"] == "https://example.test/iu-profile-page"
    assert candidate["sourceCandidateType"] == "provider_page_image"
    assert candidate["sampleFingerprints"]
    assert google_provider["usage"] == 2


def test_sqlite_store_collects_naver_blog_page_image_candidates_when_image_search_is_empty(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={"items": []},
        naver_blog_response={
            "items": [
                {
                    "title": "<b>IU</b> official blog",
                    "description": "official profile page",
                    "link": "https://example.test/iu-blog-post",
                    "bloggername": "official blog",
                    "bloggerlink": "https://blog.example.test",
                    "postdate": "20260527",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
        },
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/iu-blog-post":
            return b'<meta property="og:image" content="https://example.test/iu-blog-image.png">'
        if url == "https://example.test/iu-blog-image.png":
            return _candidate_png_bytes()
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("IU official profile", "naver")
    candidate = collection["collectionCandidates"][0]
    naver_provider = next(item for item in collection["providers"] if item["id"] == "naver")

    assert fetched_urls == [
        "https://example.test/iu-blog-post",
        "https://example.test/iu-blog-image.png",
    ]
    assert collection["collected"] == 1
    assert candidate["provider"] == "naver"
    assert candidate["imageUrl"] == "https://example.test/iu-blog-image.png"
    assert candidate["thumbnailUrl"] == "https://example.test/iu-blog-image.png"
    assert candidate["sourceUrl"] == "https://example.test/iu-blog-post"
    assert candidate["sourceCandidateType"] == "html_page_image"
    assert candidate["sampleFingerprints"]
    assert naver_provider["usage"] == 2


def test_sqlite_store_collects_naver_blog_provider_image_candidates_without_fetching_page(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={"items": []},
        naver_blog_response={
            "items": [
                {
                    "title": "<b>IU</b> official blog",
                    "description": "official profile page",
                    "link": "https://example.test/iu-blog-post",
                    "thumbnail": "https://postfiles.pstatic.net/iu-provider-thumb.jpg?type=w966",
                    "bloggername": "official blog",
                    "bloggerlink": "https://blog.example.test",
                    "postdate": "20260527",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
        },
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://postfiles.pstatic.net/iu-provider-thumb.jpg?type=w966":
            return _candidate_png_bytes()
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("IU official profile", "naver")
    candidate = collection["collectionCandidates"][0]

    assert fetched_urls == ["https://postfiles.pstatic.net/iu-provider-thumb.jpg?type=w966"]
    assert collection["collected"] == 1
    assert candidate["imageUrl"] == "https://postfiles.pstatic.net/iu-provider-thumb.jpg?type=w966"
    assert candidate["thumbnailUrl"] == "https://postfiles.pstatic.net/iu-provider-thumb.jpg?type=w966"
    assert candidate["sourceUrl"] == "https://example.test/iu-blog-post"
    assert candidate["sourceCandidateType"] == "provider_page_image"
    assert candidate["sampleFingerprints"]


def test_sqlite_store_collects_lazy_background_page_image_candidates_when_image_search_is_empty(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={"items": []},
        naver_blog_response={
            "items": [
                {
                    "title": "<b>IU</b> official profile blog",
                    "description": "profile image",
                    "link": "https://example.test/iu-blog-post",
                    "bloggername": "official blog",
                    "bloggerlink": "https://blog.example.test",
                    "postdate": "20260527",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
        },
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/iu-blog-post":
            return b'<section class="profile" data-bg="/media/iu-lazy-background.png"></section>'
        if url == "https://example.test/media/iu-lazy-background.png":
            return _candidate_png_bytes()
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("IU official profile", "naver")
    candidate = collection["collectionCandidates"][0]

    assert fetched_urls == [
        "https://example.test/iu-blog-post",
        "https://example.test/media/iu-lazy-background.png",
    ]
    assert collection["collected"] == 1
    assert candidate["imageUrl"] == "https://example.test/media/iu-lazy-background.png"
    assert candidate["sourceCandidateType"] == "html_page_image"
    assert candidate["sampleFingerprints"]


def test_sqlite_store_collects_style_block_page_image_candidates_when_image_search_is_empty(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={"items": []},
        naver_blog_response={
            "items": [
                {
                    "title": "<b>IU</b> style profile",
                    "description": "profile image",
                    "link": "https://example.test/iu-style-page",
                    "bloggername": "official blog",
                    "bloggerlink": "https://blog.example.test",
                    "postdate": "20260527",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
        },
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/iu-style-page":
            return b'<style>.profile{background:url("/media/iu-style-profile.png") center/cover}</style>'
        if url == "https://example.test/media/iu-style-profile.png":
            return _candidate_png_bytes()
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("IU official profile", "naver")
    candidate = collection["collectionCandidates"][0]

    assert fetched_urls == [
        "https://example.test/iu-style-page",
        "https://example.test/media/iu-style-profile.png",
    ]
    assert collection["collected"] == 1
    assert candidate["imageUrl"] == "https://example.test/media/iu-style-profile.png"
    assert candidate["sourceCandidateType"] == "html_page_image"
    assert candidate["sampleFingerprints"]


def test_sqlite_store_collects_stylesheet_page_image_candidates_when_image_search_is_empty(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={"items": []},
        naver_blog_response={
            "items": [
                {
                    "title": "<b>IU</b> stylesheet profile",
                    "description": "profile image",
                    "link": "https://example.test/iu-stylesheet-page",
                    "bloggername": "official blog",
                    "bloggerlink": "https://blog.example.test",
                    "postdate": "20260527",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
        },
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/iu-stylesheet-page":
            return b'<link rel="stylesheet" href="/assets/profile.css">'
        if url == "https://example.test/assets/profile.css":
            return b'.profile-hero{background-image:url("../media/iu-css-profile.png")}'
        if url == "https://example.test/media/iu-css-profile.png":
            return _candidate_png_bytes()
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("IU official profile", "naver")
    candidate = collection["collectionCandidates"][0]

    assert fetched_urls == [
        "https://example.test/iu-stylesheet-page",
        "https://example.test/assets/profile.css",
        "https://example.test/media/iu-css-profile.png",
    ]
    assert collection["collected"] == 1
    assert candidate["imageUrl"] == "https://example.test/media/iu-css-profile.png"
    assert candidate["sourceCandidateType"] == "html_page_image"
    assert candidate["sampleFingerprints"]


def test_sqlite_store_collects_preload_imagesrcset_page_image_candidates_when_image_search_is_empty(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={"items": []},
        naver_blog_response={
            "items": [
                {
                    "title": "<b>IU</b> preload profile",
                    "description": "profile image",
                    "link": "https://example.test/iu-preload-page",
                    "bloggername": "official blog",
                    "bloggerlink": "https://blog.example.test",
                    "postdate": "20260527",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
        },
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/iu-preload-page":
            return b'<link rel="preload" as="image" imagesrcset="/media/iu-small.png 320w, /media/iu-large.png 960w">'
        if url == "https://example.test/media/iu-large.png":
            return _candidate_png_bytes()
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("IU official profile", "naver")
    candidate = collection["collectionCandidates"][0]

    assert fetched_urls == [
        "https://example.test/iu-preload-page",
        "https://example.test/media/iu-large.png",
    ]
    assert collection["collected"] == 1
    assert candidate["imageUrl"] == "https://example.test/media/iu-large.png"
    assert candidate["sourceCandidateType"] == "html_page_image"
    assert candidate["sampleFingerprints"]


def test_sqlite_store_collects_next_image_optimizer_page_image_candidates_when_image_search_is_empty(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={"items": []},
        naver_blog_response={
            "items": [
                {
                    "title": "<b>IU</b> optimized profile",
                    "description": "profile image",
                    "link": "https://example.test/iu-next-image-page",
                    "bloggername": "official blog",
                    "bloggerlink": "https://blog.example.test",
                    "postdate": "20260527",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
        },
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/iu-next-image-page":
            return b'<img src="/_next/image?url=%2Fmedia%2Fiu-optimized-profile.png&w=828&q=75" alt="profile">'
        if url == "https://example.test/media/iu-optimized-profile.png":
            return _candidate_png_bytes()
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("IU official profile", "naver")
    candidate = collection["collectionCandidates"][0]

    assert fetched_urls == [
        "https://example.test/iu-next-image-page",
        "https://example.test/media/iu-optimized-profile.png",
    ]
    assert collection["collected"] == 1
    assert candidate["imageUrl"] == "https://example.test/media/iu-optimized-profile.png"
    assert candidate["sourceCandidateType"] == "html_page_image"
    assert candidate["sampleFingerprints"]


def test_sqlite_store_collects_application_json_page_image_candidates_when_image_search_is_empty(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={"items": []},
        naver_blog_response={
            "items": [
                {
                    "title": "<b>IU</b> Next profile",
                    "description": "profile image",
                    "link": "https://example.test/iu-next-page",
                    "bloggername": "official blog",
                    "bloggerlink": "https://blog.example.test",
                    "postdate": "20260527",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
        },
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/iu-next-page":
            return b"""
            <script id="__NEXT_DATA__" type="application/json">
              {"props":{"pageProps":{"profile":{"imageUrl":"/media/iu-next-profile.png"}}}}
            </script>
            """
        if url == "https://example.test/media/iu-next-profile.png":
            return _candidate_png_bytes()
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("IU official profile", "naver")
    candidate = collection["collectionCandidates"][0]

    assert fetched_urls == [
        "https://example.test/iu-next-page",
        "https://example.test/media/iu-next-profile.png",
    ]
    assert collection["collected"] == 1
    assert candidate["imageUrl"] == "https://example.test/media/iu-next-profile.png"
    assert candidate["sourceCandidateType"] == "html_page_image"
    assert candidate["sampleFingerprints"]


def test_sqlite_store_collects_direct_image_page_candidates_when_image_search_is_empty(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={"items": []},
        google_custom_web_response={
            "items": [
                {
                    "title": "IU direct profile image",
                    "link": "https://example.test/media/iu-direct-profile.png",
                    "displayLink": "example.test",
                    "snippet": "direct profile image result",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
        },
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/media/iu-direct-profile.png":
            return _candidate_png_bytes()
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("IU official profile", "google_search")
    candidate = collection["collectionCandidates"][0]

    assert fetched_urls == ["https://example.test/media/iu-direct-profile.png"]
    assert collection["collected"] == 1
    assert candidate["provider"] == "google_search"
    assert candidate["imageUrl"] == "https://example.test/media/iu-direct-profile.png"
    assert candidate["sourceUrl"] == "https://example.test/media/iu-direct-profile.png"
    assert candidate["sourceCandidateType"] == "result_page_direct_image"
    assert candidate["sampleFingerprints"]


def test_sqlite_store_collects_extensionless_direct_image_page_candidates(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={"items": []},
        google_custom_web_response={
            "items": [
                {
                    "title": "IU CDN profile image",
                    "link": "https://cdn.example.test/profile?id=iu-official",
                    "displayLink": "cdn.example.test",
                    "snippet": "direct CDN profile image result",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
        },
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://cdn.example.test/profile?id=iu-official":
            return _candidate_png_bytes()
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("IU official profile", "google_search")
    candidate = collection["collectionCandidates"][0]

    assert fetched_urls == [
        "https://cdn.example.test/profile?id=iu-official",
        "https://cdn.example.test/profile?id=iu-official",
    ]
    assert collection["collected"] == 1
    assert candidate["imageUrl"] == "https://cdn.example.test/profile?id=iu-official"
    assert candidate["sourceCandidateType"] == "result_page_direct_image"
    assert candidate["sampleFingerprints"]


def test_sqlite_store_collects_unwrapped_search_image_redirect_candidates(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={"items": []},
        google_custom_web_response={
            "items": [
                {
                    "title": "IU image redirect",
                    "link": "https://images.example.test/imgres?imgurl=https%3A%2F%2Fcdn.example.test%2Fiu-profile.png&imgrefurl=https%3A%2F%2Fexample.test%2Farticle",
                    "displayLink": "images.example.test",
                    "snippet": "image redirect result",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
        },
        transport=transport,
    )
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://cdn.example.test/iu-profile.png":
            return _candidate_png_bytes()
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("IU official profile", "google_search")
    candidate = collection["collectionCandidates"][0]

    assert fetched_urls == ["https://cdn.example.test/iu-profile.png"]
    assert collection["collected"] == 1
    assert candidate["imageUrl"] == "https://cdn.example.test/iu-profile.png"
    assert candidate["sourceUrl"].startswith("https://images.example.test/imgres")
    assert candidate["sourceCandidateType"] == "result_page_direct_image"
    assert candidate["sampleFingerprints"]


def test_sqlite_store_promotes_multiple_candidates_into_one_knowledge_entry(tmp_path: Path):
    transport = FakeJsonTransport(
        {
            "items": [
                {
                    "title": "<b>Winter</b> official profile",
                    "link": "https://example.test/winter-profile.jpg",
                    "thumbnail": "https://example.test/winter-profile-thumb.jpg",
                    "page_url": "https://example.test/winter-profile-page",
                },
                {
                    "title": "<b>Winter</b> stage image",
                    "link": "https://example.test/winter-stage.jpg",
                    "thumbnail": "https://example.test/winter-stage-thumb.jpg",
                    "page_url": "https://example.test/winter-stage-page",
                },
            ]
        }
    )
    runtime = build_provider_runtime(
        env={"NAVER_CLIENT_ID": "naver-id", "NAVER_CLIENT_SECRET": "naver-secret"},
        transport=transport,
    )
    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=lambda url: _candidate_png_bytes(
            marker="left" if "profile" in url else "right"
        ),
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    collection = store.collect_keyword_candidates("winter aespa")
    candidates = [item for item in collection["collectionCandidates"] if item["query"] == "winter aespa"]
    candidate_ids = [item["id"] for item in candidates]
    promoted = store.promote_collection_candidates(
        {
            "candidate_ids": candidate_ids,
            "name": "Winter",
            "type": "public_figure",
            "aliases": ["Winter", "Kim Min-jeong"],
            "keywords": ["aespa", "winter"],
            "memo": "Merged samples after operator review.",
        }
    )

    entry = next(item for item in promoted["knowledgeEntries"] if item["name"] == "Winter")
    promoted_candidates = [item for item in promoted["collectionCandidates"] if item["id"] in candidate_ids]

    assert len(candidates) == 2
    assert entry["type"] == "celebrity"
    assert entry["aliases"] == ["Winter", "Kim Min-jeong"]
    assert entry["keywords"] == ["aespa", "winter"]
    assert set(entry["sourceCandidates"]) == set(candidate_ids)
    assert len(entry["sampleFingerprints"]) == 2
    assert len(entry["imageAssets"]) == 2
    assert all(item["status"] == "promoted" for item in promoted_candidates)
    assert {item["promotedKnowledgeId"] for item in promoted_candidates} == {entry["id"]}


def test_sqlite_store_replaces_collection_candidates_for_each_keyword_search(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={
            "items": [
                {
                    "title": "First search image",
                    "link": "https://example.test/first.jpg",
                    "thumbnail": "https://example.test/first-thumb.jpg",
                    "page_url": "https://example.test/first-page",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={"NAVER_CLIENT_ID": "naver-id", "NAVER_CLIENT_SECRET": "naver-secret"},
        transport=transport,
    )
    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=lambda url: _candidate_png_bytes(),
    )
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))
    first_collection = store.collect_keyword_candidates("first celebrity")
    first_candidate = first_collection["collectionCandidates"][0]
    store.promote_collection_candidate(first_candidate["id"], {"name": "First Celebrity"})

    transport.naver_response = {
        "items": [
            {
                "title": "Second search image",
                "link": "https://example.test/second.jpg",
                "thumbnail": "https://example.test/second-thumb.jpg",
                "page_url": "https://example.test/second-page",
            }
        ]
    }
    second_collection = store.collect_keyword_candidates("second celebrity")

    assert [item["query"] for item in second_collection["collectionCandidates"]] == ["second celebrity"]
    assert all(item["id"] != first_candidate["id"] for item in second_collection["collectionCandidates"])
    assert any(item["name"] == "First Celebrity" for item in second_collection["knowledgeEntries"])


def test_sqlite_store_auto_runs_naver_search_from_google_entities_on_seed(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                    }
                }
            ]
        },
        naver_response={
            "items": [
                {
                    "title": "IU official profile image",
                    "link": "https://example.test/iu.jpg",
                    "thumbnail": "https://example.test/iu-thumb.jpg",
                    "page_url": "https://example.test/iu-page",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()

    store.seed_from_image_store(_image_store(tmp_path / "submissions"))
    review = store.review("SUB-DB1")

    assert any("vision.googleapis.com" in call["url"] for call in transport.calls)
    assert any("openapi.naver.com/v1/search/image" in call["url"] for call in transport.calls)
    assert review["queryHistory"][0]["provider"] == "naver"
    assert review["queryHistory"][0]["status"] == "auto"
    assert review["queryHistory"][0]["strategy"] == "google_entity"
    assert any(item["source"] == "naver" and item["status"] == "auto" for item in review["evidence"])


def test_sqlite_store_auto_runs_naver_from_specific_submission_title_when_google_has_no_query_hints(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={
            "items": [
                {
                    "title": "IU official profile image",
                    "link": "https://example.test/iu.jpg",
                    "thumbnail": "https://example.test/iu-thumb.jpg",
                    "page_url": "https://example.test/iu-page",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "1",
            "COPYRIGHTER_AUTO_GOOGLE_CUSTOM_QUERY_LIMIT": "0",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()

    store.seed_from_image_store(_titled_image_store(tmp_path / "submissions", "IU official profile"))
    review = store.review("SUB-TITLE1")
    naver_calls = [call["url"] for call in transport.calls if "openapi.naver.com/v1/search/image" in call["url"]]

    assert len(naver_calls) == 1
    assert "query=IU+official+profile" in naver_calls[0]
    assert review["queryHistory"][0]["provider"] == "naver"
    assert review["queryHistory"][0]["strategy"] == "local_metadata"
    assert any(
        item["source"] == "naver"
        and item["query"] == "IU official profile"
        and item["queryStrategy"] == "local_metadata"
        for item in review["evidence"]
    )


def test_sqlite_store_auto_runs_prioritized_naver_query_batch_from_google_evidence(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/iu-profile",
                                "score": 0.82,
                                "pageTitle": "IU official profile - Example",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={
            "items": [
                {
                    "title": "IU official profile image",
                    "link": "https://example.test/iu.jpg",
                    "thumbnail": "https://example.test/iu-thumb.jpg",
                    "page_url": "https://example.test/iu-page",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "3",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()

    store.seed_from_image_store(_image_store(tmp_path / "submissions"))
    review = store.review("SUB-DB1")
    naver_calls = [call for call in transport.calls if "openapi.naver.com/v1/search/image" in call["url"]]

    assert len(naver_calls) == 3
    assert "query=IU+official+profile" in naver_calls[0]["url"]
    assert [item["strategy"] for item in review["queryHistory"] if item["provider"] == "naver"] == [
        "google_page",
        "google_page",
        "google_entity",
    ]
    assert len([item for item in review["evidence"] if item["source"] == "naver" and item["searchType"] not in {"blog", "web"}]) == 3


def test_sqlite_store_seed_runs_naver_before_google_custom(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                    }
                }
            ]
        },
        naver_response={
            "items": [
                {
                    "title": "IU official profile image",
                    "link": "https://example.test/iu.jpg",
                    "thumbnail": "https://example.test/iu-thumb.jpg",
                    "page_url": "https://example.test/iu-page",
                }
            ]
        },
        google_custom_image_response={
            "items": [
                {
                    "title": "IU official profile from google custom",
                    "link": "https://example.test/google-custom-iu.png",
                    "displayLink": "example.test",
                    "snippet": "profile image",
                    "image": {
                        "thumbnailLink": "https://example.test/google-custom-iu-thumb.png",
                        "contextLink": "https://example.test/google-custom-profile",
                    },
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "1",
            "COPYRIGHTER_AUTO_GOOGLE_CUSTOM_QUERY_LIMIT": "1",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()

    store.seed_from_image_store(_image_store(tmp_path / "submissions"))
    naver_index = next(
        index
        for index, call in enumerate(transport.calls)
        if "openapi.naver.com/v1/search/image" in call["url"]
    )
    google_custom_index = next(
        index
        for index, call in enumerate(transport.calls)
        if "www.googleapis.com/customsearch/v1" in call["url"]
    )

    assert naver_index < google_custom_index


def test_sqlite_store_auto_naver_calls_blog_and_web_even_if_image_hits_match(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={},
        naver_response={
            "items": [
                {
                    "title": "IU official profile image",
                    "link": "https://example.test/naver-result.png",
                    "thumbnail": "https://example.test/naver-result-thumb.png",
                    "page_url": "https://example.test/naver-result-page",
                }
            ]
        },
        naver_blog_response={
            "items": [
                {
                    "title": "IU official blog",
                    "description": "profile",
                    "link": "https://example.test/blog-post",
                    "bloggername": "official",
                    "bloggerlink": "https://blog.example.test",
                    "postdate": "20260527",
                }
            ]
        },
        naver_web_response={
            "items": [
                {
                    "title": "IU official web page",
                    "description": "official page",
                    "link": "https://example.test/web-page",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "1",
            "COPYRIGHTER_AUTO_NAVER_BLOG_QUERY_LIMIT": "1",
            "COPYRIGHTER_AUTO_NAVER_WEB_QUERY_LIMIT": "1",
            "COPYRIGHTER_SEARCH_RESULT_COMPARE_LIMIT": "3",
            "COPYRIGHTER_SEARCH_RESULT_SIMILARITY_THRESHOLD": "0.3",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_dir = tmp_path / "images"
    image_dir.mkdir(parents=True)
    from PIL import Image

    image_bytes = BytesIO()
    Image.new("RGB", (160, 120), color=(230, 220, 210)).save(image_bytes, format="PNG")
    (image_dir / "portrait.png").write_bytes(image_bytes.getvalue())
    (tmp_path / "submissions.json").write_text(
        json.dumps(
            [
                {
                    "id": "SUB-PNG1",
                    "title": "IU official profile",
                    "file": "images/portrait.png",
                    "width": 160,
                    "height": 120,
                    "submitted_at": "2026-05-26 10:30",
                }
            ],
            ensure_ascii=False,
        ),
        encoding="utf-8",
    )
    image_store = LocalSubmissionImageStore(tmp_path)
    submitted_image = image_store.image_payload("SUB-PNG1").content

    def fetcher(url: str) -> bytes:
        if url == "https://example.test/naver-result.png":
            return submitted_image
        if url == "https://example.test/naver-result-thumb.png":
            return submitted_image
        if url == "https://example.test/blog-post":
            return b'<meta property="og:image" content="https://example.test/blog-image.png">'
        if url == "https://example.test/web-page":
            return b'<meta property="og:image" content="https://example.test/web-image.png">'
        if url in {"https://example.test/blog-image.png", "https://example.test/web-image.png"}:
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    naver_calls = [call["url"] for call in transport.calls if "openapi.naver.com/v1/search" in call["url"]]

    assert any("/v1/search/image" in url for url in naver_calls)
    assert any("/v1/search/blog" in url for url in naver_calls)
    assert any("/v1/search/webkr" in url for url in naver_calls)
    providers = [item["provider"] for item in review["queryHistory"]]
    assert providers[0] == "naver"
    assert providers[1] == "naver_blog"
    assert providers[2] == "naver_web"
    assert any(
        item["source"] == "fingerprint"
        and item["matchType"] == "search_result_image"
        and item["remoteImageUrl"] == "https://example.test/naver-result.png"
        for item in review["evidence"]
    )
    assert any(
        item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
        and item["remoteImageUrl"] == "https://example.test/blog-image.png"
        for item in review["evidence"]
    )
    assert any(
        item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
        and item["remoteImageUrl"] == "https://example.test/web-image.png"
        for item in review["evidence"]
    )


def test_sqlite_store_compares_downloaded_search_result_images_against_submission(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                    }
                }
            ]
        },
        naver_response={
            "items": [
                {
                    "title": "IU official profile image",
                    "link": "https://example.test/iu.png",
                    "thumbnail": "https://example.test/iu-thumb.png",
                    "page_url": "https://example.test/iu-page",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "1",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=lambda url: image_store.image_payload("SUB-PNG1").content,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    remote_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_image"
    )

    assert remote_match["similarity"] >= 0.9
    assert remote_match["thumbnailUrl"].startswith("/collected-media/")
    assert remote_match["queryStrategy"] == "google_entity"
    assert remote_match["sourceEvidenceIds"]
    assert review["riskBand"] == "high"


def test_sqlite_store_compares_google_vision_image_url_variants_against_submission(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "fullMatchingImages": [
                            {
                                "imageUrl": "https://cdn.example.test/vision-provider-image.webp",
                                "thumbnailUrl": "https://cdn.example.test/vision-provider-thumb.webp",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://cdn.example.test/vision-provider-image.webp":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    remote_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_image"
    )

    assert fetched_urls == ["https://cdn.example.test/vision-provider-image.webp"]
    assert remote_match["remoteImageUrl"] == "https://cdn.example.test/vision-provider-image.webp"
    assert remote_match["imageCandidateSource"] == "result_image_url"
    assert remote_match["similarity"] >= 0.9
    assert review["riskBand"] == "high"


def test_search_result_candidate_image_similarity_respects_runtime_threshold(
    tmp_path: Path,
    monkeypatch,
):
    image_store = _png_image_store(tmp_path / "submissions")
    original_image = image_store.image_payload("SUB-PNG1").content
    fetcher = lambda _: original_image

    runtime = build_provider_runtime(
        env={"COPYRIGHTER_SEARCH_RESULT_SIMILARITY_THRESHOLD": "0.95"},
        transport=FakeJsonTransport({}),
    )
    runtime.llm_assistant = None
    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    source_evidence = Evidence(
        source=EvidenceSource.WEB_DETECTION,
        reason="Google web result",
        confidence=0.9,
        data={"query": "IU", "provider": "google", "result_url": "https://example.test/source"},
    )

    monkeypatch.setattr(sqlite_store_module.FingerprintService, "similarity", lambda *args: 0.92)
    assert (
        store._search_result_candidate_image_evidence(
            "SUB-PNG1",
            "submission-fingerprint",
            source_evidence,
            "https://example.test/iu.png",
            "search_result_image",
            "result_image_url",
        )
        is None
    )

    runtime.search_result_similarity_threshold = 0.9
    match = store._search_result_candidate_image_evidence(
        "SUB-PNG1",
        "submission-fingerprint",
        source_evidence,
        "https://example.test/iu.png",
        "search_result_image",
        "result_image_url",
    )
    assert match is not None
    assert match.data["similarity"] == 0.92


def test_sqlite_store_compares_unwrapped_search_result_image_urls_against_submission(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                    }
                }
            ]
        },
        naver_response={
            "items": [
                {
                    "title": "IU official profile image",
                    "link": "https://search.example.test/proxy?imgurl=https%3A%2F%2Fcdn.example.test%2Fiu-official.png",
                    "thumbnail": "https://search.example.test/thumb?imgurl=https%3A%2F%2Fcdn.example.test%2Fiu-thumb.png",
                    "page_url": "https://example.test/iu-page",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "1",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://cdn.example.test/iu-official.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    remote_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_image"
    )

    assert fetched_urls == ["https://cdn.example.test/iu-official.png"]
    assert remote_match["remoteImageUrl"] == "https://cdn.example.test/iu-official.png"
    assert remote_match["sourcePageUrl"] == "https://example.test/iu-page"
    assert remote_match["imageCandidateSource"] == "result_image_url"
    assert remote_match["similarity"] >= 0.9
    assert review["riskBand"] == "high"


def test_search_result_similarity_collects_multiple_matches_within_limit(tmp_path: Path):
    image_store = _png_image_store(tmp_path / "submissions")
    original_image = image_store.image_payload("SUB-PNG1").content
    fetcher = lambda _: original_image

    runtime = build_provider_runtime(
        env={"COPYRIGHTER_SEARCH_RESULT_COMPARE_LIMIT": "3"},
        transport=FakeJsonTransport({}),
    )
    runtime.llm_assistant = None
    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    store.seed_from_image_store(image_store)

    evidence = [
        Evidence(
            source=EvidenceSource.NAVER_SEARCH,
            reason="Naver result A",
            confidence=0.9,
            data={
                "query": "IU",
                "provider": "naver",
                "image_url": "https://example.test/iu-a.png",
                "result_url": "https://example.test/source-a",
            },
        ),
        Evidence(
            source=EvidenceSource.NAVER_SEARCH,
            reason="Naver result B",
            confidence=0.9,
            data={
                "query": "IU",
                "provider": "naver",
                "image_url": "https://example.test/iu-b.png",
                "result_url": "https://example.test/source-b",
            },
        ),
    ]

    matches = store._sync_search_result_image_similarity(
        "SUB-PNG1",
        evidence,
        image_store,
    )

    assert len(matches) >= 2
    assert len(matches) <= runtime.search_result_compare_limit
    assert any(match.data["match"] == "search_result_image" for match in matches)
    assert matches[0].data["image_candidate_source"] == "result_image_url"
    review = store.review("SUB-PNG1")
    search_fingerprint_evidence = [
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint" and str(item["matchType"]).startswith("search_result")
    ]
    assert len(search_fingerprint_evidence) >= 2
    assert len(search_fingerprint_evidence) <= runtime.search_result_compare_limit


def test_sqlite_store_compares_scheme_less_unwrapped_search_result_image_urls(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                    }
                }
            ]
        },
        naver_response={
            "items": [
                {
                    "title": "IU official profile image",
                    "link": "https://search.example.test/proxy?imgurl=cdn.example.test%2Fiu-official.png",
                    "thumbnail": "https://search.example.test/thumb?imgurl=cdn.example.test%2Fiu-thumb.png",
                    "page_url": "https://example.test/iu-page",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "1",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://cdn.example.test/iu-official.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    remote_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_image"
    )

    assert fetched_urls == ["https://cdn.example.test/iu-official.png"]
    assert remote_match["remoteImageUrl"] == "https://cdn.example.test/iu-official.png"
    assert remote_match["imageCandidateSource"] == "result_image_url"
    assert remote_match["similarity"] >= 0.9
    assert review["riskBand"] == "high"


def test_sqlite_store_compares_scheme_less_query_format_search_result_image_urls(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                    }
                }
            ]
        },
        naver_response={
            "items": [
                {
                    "title": "IU official profile image",
                    "link": "https://search.example.test/proxy?imgurl=cdn.example.test%2Frender%3Fformat%3Dwebp",
                    "thumbnail": "https://search.example.test/thumb?imgurl=cdn.example.test%2Fthumb%3Ffm%3Djpg",
                    "page_url": "https://example.test/iu-page",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "1",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://cdn.example.test/render?format=webp":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    remote_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_image"
    )

    assert fetched_urls == ["https://cdn.example.test/render?format=webp"]
    assert remote_match["remoteImageUrl"] == "https://cdn.example.test/render?format=webp"
    assert remote_match["imageCandidateSource"] == "result_image_url"
    assert remote_match["similarity"] >= 0.9
    assert review["riskBand"] == "high"


def test_sqlite_store_compares_search_result_thumbnail_when_original_image_fails(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                    }
                }
            ]
        },
        naver_response={
            "items": [
                {
                    "title": "IU official profile image",
                    "link": "https://example.test/iu-original.png",
                    "thumbnail": "https://example.test/iu-thumb.png",
                    "page_url": "https://example.test/iu-page",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "1",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/iu-thumb.png":
            return submitted_image
        raise ValueError(f"image unavailable: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    remote_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_image"
    )

    assert fetched_urls == [
        "https://example.test/iu-original.png",
        "https://example.test/iu-thumb.png",
    ]
    assert remote_match["remoteImageUrl"] == "https://example.test/iu-thumb.png"
    assert remote_match["imageCandidateSource"] == "result_image_url"
    assert remote_match["similarity"] >= 0.9
    assert review["riskBand"] == "high"


def test_sqlite_store_exposes_provider_page_image_as_google_page_preview(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity image source page",
                                "fullMatchingImages": [
                                    {"url": "https://cdn.example.test/provider-match.jpg"}
                                ],
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_evidence = next(
        item
        for item in review["evidence"]
        if item["source"] == "google" and item["matchType"] == "page"
    )

    assert page_evidence["thumbnailUrl"] == "https://cdn.example.test/provider-match.jpg"
    assert page_evidence["pageImageUrls"] == ["https://cdn.example.test/provider-match.jpg"]


def test_sqlite_store_compares_representative_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity image source page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <meta property="og:image" content="/media/portrait.png">
              </head>
              <body></body>
            </html>
            """
        if url == "https://example.test/media/portrait.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/portrait.png",
    ]
    assert page_image_match["similarity"] >= 0.9
    assert page_image_match["url"] == "https://example.test/source-page"
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/portrait.png"
    assert page_image_match["sourcePageUrl"] == "https://example.test/source-page"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert page_image_match["thumbnailUrl"].startswith("/collected-media/")
    assert page_image_match["sourceEvidenceIds"]
    assert review["riskBand"] == "high"


def test_sqlite_store_decodes_percent_encoded_meta_image_urls_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Encoded og image source page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <meta property="og:image" content="https%3A%2F%2Fcdn.example.test%2Fencoded-og-profile.png">
              </head>
              <body></body>
            </html>
            """
        if url == "https://cdn.example.test/encoded-og-profile.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://cdn.example.test/encoded-og-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://cdn.example.test/encoded-og-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_uses_page_and_image_specific_headers_for_default_fetcher(
    tmp_path: Path,
    monkeypatch,
):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity image source page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    seen: list[tuple[str, str]] = []

    class FakeResponse:
        def __init__(self, content: bytes):
            self.content = content

        def __enter__(self):
            return self

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

        def read(self, size=-1):
            return self.content

    def fake_urlopen(request, timeout):
        headers = {
            key.lower(): value
            for key, value in request.header_items()
        }
        accept = headers.get("accept", "")
        seen.append((request.full_url, accept))
        if request.full_url == "https://example.test/source-page":
            if "text/html" not in accept:
                raise ValueError("page fetch did not request HTML")
            return FakeResponse(
                b"""
                <html>
                  <head>
                    <meta property="og:image" content="/media/portrait.png">
                  </head>
                </html>
                """
            )
        if request.full_url == "https://example.test/media/portrait.png":
            if not accept.startswith("image/"):
                raise ValueError("image fetch did not request image content")
            return FakeResponse(submitted_image)
        raise AssertionError(f"unexpected URL fetched: {request.full_url}")

    monkeypatch.setattr(sqlite_store_module, "_open_url", fake_urlopen)
    monkeypatch.setattr(sqlite_store_module, "_assert_public_http_url", lambda url: None)
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert seen == [
        ("https://example.test/source-page", seen[0][1]),
        ("https://example.test/media/portrait.png", seen[1][1]),
    ]
    assert "text/html" in seen[0][1]
    assert seen[1][1].startswith("image/")
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/portrait.png"
    assert review["riskBand"] == "high"


def test_sqlite_store_uses_source_page_referer_for_default_page_image_fetch(
    tmp_path: Path,
    monkeypatch,
):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity image source page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    seen_referers: list[tuple[str, str]] = []

    class FakeResponse:
        def __init__(self, content: bytes):
            self.content = content

        def __enter__(self):
            return self

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

        def read(self, size=-1):
            return self.content

    def fake_urlopen(request, timeout):
        headers = {
            key.lower(): value
            for key, value in request.header_items()
        }
        seen_referers.append((request.full_url, headers.get("referer", "")))
        if request.full_url == "https://example.test/source-page":
            return FakeResponse(
                b"""
                <html>
                  <head>
                    <meta property="og:image" content="/media/hotlink-protected.png">
                  </head>
                </html>
                """
            )
        if request.full_url == "https://example.test/media/hotlink-protected.png":
            if headers.get("referer") != "https://example.test/source-page":
                raise ValueError("hotlink-protected image requires source page referer")
            return FakeResponse(submitted_image)
        raise AssertionError(f"unexpected URL fetched: {request.full_url}")

    monkeypatch.setattr(sqlite_store_module, "_open_url", fake_urlopen)
    monkeypatch.setattr(sqlite_store_module, "_assert_public_http_url", lambda url: None)
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert seen_referers == [
        ("https://example.test/source-page", ""),
        ("https://example.test/media/hotlink-protected.png", "https://example.test/source-page"),
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/hotlink-protected.png"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_srcset_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity image source page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <picture>
                  <source srcset="/media/portrait-small.png 320w, /media/portrait-large.png 960w">
                  <img data-srcset="/media/lazy-small.png 320w, /media/lazy-large.png 960w" alt="profile">
                </picture>
              </body>
            </html>
            """
        if url == "https://example.test/media/portrait-large.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/portrait-large.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/portrait-large.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_srcset_urls_with_query_commas_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Responsive CDN profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <img
                  alt="profile"
                  srcset="https://cdn.example.test/render?img=profile-small&ops=resize:320,webp 320w, https://cdn.example.test/render?img=profile-large&ops=resize:960,webp 960w">
              </body>
            </html>
            """
        if url == "https://cdn.example.test/render?img=profile-large&ops=resize:960,webp":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://cdn.example.test/render?img=profile-large&ops=resize:960,webp",
    ]
    assert page_image_match["remoteImageUrl"] == "https://cdn.example.test/render?img=profile-large&ops=resize:960,webp"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_lazy_srcset_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Lazy responsive celebrity image page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <img
                  data-lazy-srcset="/media/profile-small.png 320w, /media/profile-large.png 960w"
                  data-original-srcset="/media/original-small.png 320w, /media/original-large.png 1280w"
                  alt="profile">
              </body>
            </html>
            """
        if url == "https://example.test/media/original-large.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/original-large.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/original-large.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_prioritizes_lazy_img_source_over_placeholder_src(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Lazy placeholder celebrity image page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "COPYRIGHTER_SEARCH_RESULT_PAGE_IMAGE_LIMIT": "1",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <img
                  class="profile-photo hero"
                  src="/assets/transparent-placeholder.png"
                  data-src="/media/lazy-real-profile.png"
                  alt="official profile photo">
              </body>
            </html>
            """
        if url == "https://example.test/media/lazy-real-profile.png":
            return submitted_image
        raise AssertionError(f"placeholder or unexpected URL fetched first: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/lazy-real-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/lazy-real-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_lazy_data_image_url_attrs_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Lazy loaded celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <div
                  class="profile-photo"
                  data-image-url="/media/profile-data-url.png"
                  data-thumbnail-url="/media/profile-thumb.png">
                </div>
              </body>
            </html>
            """
        if url == "https://example.test/media/profile-data-url.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/profile-data-url.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/profile-data-url.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_image_like_custom_data_attrs_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Custom lazy data celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <div
                  class="profile-photo"
                  data-profile-photo-url="/media/profile-custom-photo.png"
                  data-navigation-url="/not-an-image-page">
                </div>
              </body>
            </html>
            """
        if url == "https://example.test/media/profile-custom-photo.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/profile-custom-photo.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/profile-custom-photo.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_json_data_image_attrs_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "JSON data image celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <div
                  class="profile-photo"
                  data-image='{"src": "/media/profile-data-json.png", "alt": "profile portrait"}'>
                </div>
              </body>
            </html>
            """
        if url == "https://example.test/media/profile-data-json.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/profile-data-json.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/profile-data-json.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_json_props_data_attrs_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "JSON props celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <div
                  data-props='{"profile": {"avatar": {"url": "/media/profile-data-props.png"}}}'>
                </div>
              </body>
            </html>
            """
        if url == "https://example.test/media/profile-data-props.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/profile-data-props.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/profile-data-props.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_nested_json_props_public_url_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "JSON props public URL celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <div
                  data-props='{"profile": {"representativeImage": {"file": {"publicUrl": "/media/profile-public-url.png"}}}}'>
                </div>
              </body>
            </html>
            """
        if url == "https://example.test/media/profile-public-url.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/profile-public-url.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/profile-public-url.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_url_encoded_json_data_attrs_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Encoded JSON state celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <div
                  data-state="%7B%22profile%22%3A%7B%22image%22%3A%7B%22src%22%3A%22%2Fmedia%2Fprofile-data-encoded.png%22%7D%7D%7D">
                </div>
              </body>
            </html>
            """
        if url == "https://example.test/media/profile-data-encoded.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/profile-data-encoded.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/profile-data-encoded.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_custom_data_srcset_attrs_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Custom data srcset celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <div
                  class="profile-photo"
                  data-profile-srcset="/media/profile-srcset-small.png 320w, /media/profile-srcset-large.png 960w">
                </div>
              </body>
            </html>
            """
        if url == "https://example.test/media/profile-srcset-large.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/profile-srcset-large.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/profile-srcset-large.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_video_poster_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity profile video page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <video poster="/media/profile-video-poster.png" controls></video>
              </body>
            </html>
            """
        if url == "https://example.test/media/profile-video-poster.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/profile-video-poster.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/profile-video-poster.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_bmp_video_poster_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity video page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <video poster="/media/profile-video-poster.bmp"></video>
              </body>
            </html>
            """
        if url == "https://example.test/media/profile-video-poster.bmp":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/profile-video-poster.bmp",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/profile-video-poster.bmp"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_amp_img_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "AMP celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html amp>
              <body>
                <amp-img
                  src="/media/amp-profile-small.png"
                  srcset="/media/amp-profile-small.png 320w, /media/amp-profile-large.png 960w"
                  alt="profile">
                </amp-img>
              </body>
            </html>
            """
        if url == "https://example.test/media/amp-profile-large.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/amp-profile-large.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/amp-profile-large.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_json_ld_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity image source page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <script type="application/ld+json">
                  {
                    "@context": "https://schema.org",
                    "@type": "NewsArticle",
                    "image": {
                      "@type": "ImageObject",
                      "contentUrl": "/media/jsonld-portrait.png"
                    }
                  }
                </script>
              </head>
              <body>
                <img src="/media/body-fallback.png" alt="lower priority image">
              </body>
            </html>
            """
        if url == "https://example.test/media/jsonld-portrait.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/jsonld-portrait.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/jsonld-portrait.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_application_json_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Next.js celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <script id="__NEXT_DATA__" type="application/json">
                  {
                    "props": {
                      "pageProps": {
                        "profile": {
                          "name": "IU",
                          "imageUrl": "/media/next-profile.png"
                        }
                      }
                    }
                  }
                </script>
              </head>
              <body>
                <img src="/media/body-fallback.png" alt="lower priority image">
              </body>
            </html>
            """
        if url == "https://example.test/media/next-profile.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/next-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/next-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_extensionless_application_json_image_urls_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "CDN rendered celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <script id="__NEXT_DATA__" type="application/json">
                  {
                    "props": {
                      "pageProps": {
                        "profile": {
                          "image": {
                            "url": "https://cdn.example.test/render?id=profile-main"
                          }
                        }
                      }
                    }
                  }
                </script>
              </head>
            </html>
            """
        if url == "https://cdn.example.test/render?id=profile-main":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://cdn.example.test/render?id=profile-main",
    ]
    assert page_image_match["remoteImageUrl"] == "https://cdn.example.test/render?id=profile-main"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_percent_encoded_application_json_image_urls_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Encoded JSON celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <script id="__NEXT_DATA__" type="application/json">
                  {
                    "props": {
                      "pageProps": {
                        "profile": {
                          "imageUrl": "https%3A%2F%2Fcdn.example.test%2Fencoded-profile.png"
                        }
                      }
                    }
                  }
                </script>
              </head>
            </html>
            """
        if url == "https://cdn.example.test/encoded-profile.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://cdn.example.test/encoded-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://cdn.example.test/encoded-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_application_json_content_url_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "JSON media celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <script type="application/json">
                  {
                    "props": {
                      "pageProps": {
                        "profile": {
                          "name": "IU",
                          "contentUrl": "/media/json-content-url-profile.png",
                          "canonicalUrl": "https://example.test/not-an-image-page"
                        }
                      }
                    }
                  }
                </script>
              </head>
              <body></body>
            </html>
            """
        if url == "https://example.test/media/json-content-url-profile.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/json-content-url-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/json-content-url-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_nested_application_json_image_file_urls_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "React profile page with nested image file URL",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <script id="__NEXT_DATA__" type="application/json">
                  {
                    "props": {
                      "pageProps": {
                        "profile": {
                          "name": "IU",
                          "representativeImage": {
                            "file": {
                              "url": "/media/nested-file-profile.png"
                            },
                            "alt": "official profile"
                          }
                        }
                      }
                    }
                  }
                </script>
              </head>
              <body></body>
            </html>
            """
        if url == "https://example.test/media/nested-file-profile.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/nested-file-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/nested-file-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_application_json_srcset_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "React profile page with JSON srcSet",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <script id="__NEXT_DATA__" type="application/json">
                  {
                    "props": {
                      "pageProps": {
                        "profile": {
                          "name": "IU",
                          "image": {
                            "srcSet": "/media/json-srcset-small.png 320w, /media/json-srcset-large.png 960w"
                          }
                        }
                      }
                    }
                  }
                </script>
              </head>
              <body></body>
            </html>
            """
        if url == "https://example.test/media/json-srcset-large.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/json-srcset-large.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/json-srcset-large.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_next_optimizer_images_from_application_json_state(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Next.js hidden optimizer profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <script id="__NEXT_DATA__" type="application/json">
                  {
                    "props": {
                      "pageProps": {
                        "profile": {
                          "name": "IU",
                          "imageUrl": "/_next/image?url=%2Fmedia%2Fnext-hidden-profile.png&w=828&q=75"
                        }
                      }
                    }
                  }
                </script>
              </head>
              <body></body>
            </html>
            """
        if url == "https://example.test/media/next-hidden-profile.png":
            return submitted_image
        if "next" in url or url.endswith(".png"):
            raise AssertionError(f"optimizer URL was not unwrapped before fetch: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/next-hidden-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/next-hidden-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_inline_javascript_state_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "SPA celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <script>
                  window.__INITIAL_STATE__ = {
                    profile: {
                      name: "IU",
                      imageUrl: "/media/script-profile.png",
                      canonicalUrl: "https://example.test/plain-page"
                    }
                  };
                </script>
              </head>
              <body>
                <img src="/media/body-fallback.png" alt="lower priority image">
              </body>
            </html>
            """
        if url == "https://example.test/media/script-profile.png":
            return submitted_image
        if url == "https://example.test/media/body-fallback.png":
            return _candidate_png_bytes((30, 40, 50), marker="right")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/script-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/script-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_inline_javascript_srcset_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "SPA celebrity srcSet profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <script>
                  window.__INITIAL_STATE__ = {
                    profile: {
                      name: "IU",
                      image: {
                        srcSet: "/media/script-srcset-small.png 320w, /media/script-srcset-large.png 960w"
                      }
                    }
                  };
                </script>
              </head>
              <body></body>
            </html>
            """
        if url == "https://example.test/media/script-srcset-large.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/script-srcset-large.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/script-srcset-large.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_escaped_noscript_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Lazy-loaded celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <noscript>
                  &lt;img src=&quot;/media/noscript-profile.png&quot; alt=&quot;profile&quot;&gt;
                </noscript>
                <img src="/media/body-placeholder.png" alt="placeholder">
              </body>
            </html>
            """
        if url == "https://example.test/media/noscript-profile.png":
            return submitted_image
        if url == "https://example.test/media/body-placeholder.png":
            return _candidate_png_bytes((30, 40, 50), marker="right")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/noscript-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/noscript-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_direct_anchor_image_links_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Gallery celebrity profile page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <a class="viewer" href="/media/full-profile.png">open original</a>
                <img src="/media/body-placeholder.png" alt="placeholder">
              </body>
            </html>
            """
        if url == "https://example.test/media/full-profile.png":
            return submitted_image
        if url == "https://example.test/media/body-placeholder.png":
            return _candidate_png_bytes((30, 40, 50), marker="right")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/full-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/full-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_prioritizes_likely_profile_images_over_page_chrome(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity profile with site chrome",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "COPYRIGHTER_SEARCH_RESULT_PAGE_IMAGE_LIMIT": "3",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <img src="/assets/site-logo.png" alt="Example logo">
                <img src="/assets/menu-icon.png" class="icon" alt="menu">
                <img src="/ads/banner.png" alt="advertisement">
                <img
                  src="/media/official-profile-photo.png"
                  width="900"
                  height="1200"
                  class="profile-photo"
                  alt="IU official profile photo">
              </body>
            </html>
            """
        if url == "https://example.test/media/official-profile-photo.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"page chrome image fetched before profile image: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/official-profile-photo.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/official-profile-photo.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_itemprop_and_preload_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity image source page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <meta itemprop="image" content="/media/itemprop-portrait.png">
                <link rel="preload" as="image" href="/media/preload-portrait.png">
              </head>
            </html>
            """
        if url == "https://example.test/media/itemprop-portrait.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/itemprop-portrait.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/itemprop-portrait.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_hyphenated_image_src_link_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity page with image link",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <link rel="image-src" href="/media/hyphen-link-profile.png">
              </head>
              <body>
                <img src="/assets/page-logo.png" alt="site logo">
              </body>
            </html>
            """
        if url == "https://example.test/media/hyphen-link-profile.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/hyphen-link-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/hyphen-link-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_itemprop_content_url_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity ImageObject page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body itemscope itemtype="https://schema.org/ImageObject">
                <meta itemprop="contentUrl" content="/media/itemprop-content-url.png">
              </body>
            </html>
            """
        if url == "https://example.test/media/itemprop-content-url.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/itemprop-content-url.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/itemprop-content-url.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_meta_thumbnail_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity page with thumbnail meta",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <meta name="thumbnail" content="/media/meta-thumbnail.png">
              </head>
              <body>
                <img src="/assets/page-logo.png" alt="site logo">
              </body>
            </html>
            """
        if url == "https://example.test/media/meta-thumbnail.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/meta-thumbnail.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/meta-thumbnail.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_preload_imagesrcset_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity responsive image page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <link rel="preload" as="image" imagesrcset="/media/profile-small.png 320w, /media/profile-large.png 960w">
              </head>
            </html>
            """
        if url == "https://example.test/media/profile-large.png":
            return submitted_image
        if url.endswith(".png"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/profile-large.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/profile-large.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_next_image_optimizer_urls_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Next.js optimized image page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <img src="/_next/image?url=%2Fmedia%2Foptimized-profile.png&w=828&q=75" alt="profile">
              </body>
            </html>
            """
        if url == "https://example.test/media/optimized-profile.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/optimized-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/optimized-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_inline_background_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity image source page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <section class="profile-hero" style="background-image: url('/media/background-portrait.png')"></section>
              </body>
            </html>
            """
        if url == "https://example.test/media/background-portrait.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/background-portrait.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/background-portrait.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_style_block_background_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity image source page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <style>
                  .profile-hero {
                    background-image: url("/media/style-block-profile.png");
                  }
                </style>
              </head>
              <body></body>
            </html>
            """
        if url == "https://example.test/media/style-block-profile.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/style-block-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/style-block-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_stylesheet_background_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "External stylesheet image page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <link rel="stylesheet" href="/assets/profile.css">
              </head>
            </html>
            """
        if url == "https://example.test/assets/profile.css":
            return b'.profile-hero{background-image:url("../media/css-profile.png")}'
        if url == "https://example.test/media/css-profile.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/assets/profile.css",
        "https://example.test/media/css-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/css-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_css_image_set_images_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "CSS image-set celebrity page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <head>
                <style>
                  .profile-hero {
                    background-image: image-set(
                      "/media/css-profile-small.webp" 1x,
                      "/media/css-profile-large.webp" 2x
                    );
                  }
                </style>
              </head>
              <body></body>
            </html>
            """
        if url == "https://example.test/media/css-profile-large.webp":
            return submitted_image
        if url.endswith(".webp"):
            raise AssertionError(f"lower priority image fetched first: {url}")
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://example.test/media/css-profile-large.webp",
    ]
    assert page_image_match["remoteImageUrl"] == "https://example.test/media/css-profile-large.webp"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_unwrapped_proxy_image_urls_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Celebrity image source page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <img src="/proxy?imgurl=https%3A%2F%2Fcdn.example.test%2Fproxy-profile.png" alt="profile">
              </body>
            </html>
            """
        if url == "https://cdn.example.test/proxy-profile.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://cdn.example.test/proxy-profile.png",
    ]
    assert page_image_match["remoteImageUrl"] == "https://cdn.example.test/proxy-profile.png"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_extracts_query_format_proxy_image_urls_from_search_result_pages(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "pageTitle": "Proxy image page",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/source-page":
            return b"""
            <html>
              <body>
                <img src="/proxy?url=https%3A%2F%2Fcdn.example.test%2Frender%3Fformat%3Dwebp" alt="profile">
              </body>
            </html>
            """
        if url == "https://cdn.example.test/render?format=webp":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://example.test/source-page",
        "https://cdn.example.test/render?format=webp",
    ]
    assert page_image_match["remoteImageUrl"] == "https://cdn.example.test/render?format=webp"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_compares_direct_image_search_result_urls(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/media/direct-result.png",
                                "pageTitle": "Direct image search result",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/media/direct-result.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    direct_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == ["https://example.test/media/direct-result.png"]
    assert direct_match["remoteImageUrl"] == "https://example.test/media/direct-result.png"
    assert direct_match["sourcePageUrl"] == "https://example.test/media/direct-result.png"
    assert direct_match["imageCandidateSource"] == "result_page_direct_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_compares_extensionless_direct_image_search_result_responses(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://cdn.example.test/profile?id=official",
                                "pageTitle": "Extensionless CDN image result",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://cdn.example.test/profile?id=official":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    direct_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == [
        "https://cdn.example.test/profile?id=official",
        "https://cdn.example.test/profile?id=official",
    ]
    assert direct_match["remoteImageUrl"] == "https://cdn.example.test/profile?id=official"
    assert direct_match["imageCandidateSource"] == "result_page_direct_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_compares_unwrapped_search_image_redirect_results(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://images.example.test/imgres?imgurl=https%3A%2F%2Fcdn.example.test%2Fofficial-profile.png&imgrefurl=https%3A%2F%2Fexample.test%2Farticle",
                                "pageTitle": "Image redirect result",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://cdn.example.test/official-profile.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    redirect_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
    )

    assert fetched_urls == ["https://cdn.example.test/official-profile.png"]
    assert redirect_match["remoteImageUrl"] == "https://cdn.example.test/official-profile.png"
    assert redirect_match["sourcePageUrl"].startswith("https://images.example.test/imgres")
    assert redirect_match["imageCandidateSource"] == "result_page_direct_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_falls_back_to_naver_blog_page_images_when_image_search_is_empty(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                    }
                }
            ]
        },
        naver_response={"items": []},
        naver_blog_response={
            "items": [
                {
                    "title": "IU official profile blog",
                    "description": "profile image",
                    "link": "https://example.test/blog-post",
                    "bloggername": "official blog",
                    "bloggerlink": "https://blog.example.test",
                    "postdate": "20260527",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "1",
            "COPYRIGHTER_AUTO_NAVER_BLOG_QUERY_LIMIT": "1",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content

    def fetcher(url: str) -> bytes:
        if url == "https://example.test/blog-post":
            return b'<meta property="og:image" content="https://example.test/blog-image.png">'
        if url == "https://example.test/blog-image.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    naver_calls = [call["url"] for call in transport.calls if "openapi.naver.com/v1/search" in call["url"]]
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
        and item["remoteImageUrl"] == "https://example.test/blog-image.png"
    )

    assert any("/v1/search/image" in url for url in naver_calls)
    assert any("/v1/search/blog" in url for url in naver_calls)
    assert any(
        item["source"] == "naver"
        and item["domain"] == "naver"
        and item["url"] == "https://example.test/blog-post"
        and item["searchType"] == "blog"
        and item["bloggerName"] == "official blog"
        and item["matchType"] == "page"
        for item in review["evidence"]
    )
    assert any(item["provider"] == "naver_blog" for item in review["queryHistory"])
    assert page_image_match["sourcePageUrl"] == "https://example.test/blog-post"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_falls_back_to_naver_web_page_images_when_image_and_blog_are_empty(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                    }
                }
            ]
        },
        naver_response={"items": []},
        naver_blog_response={"items": []},
        naver_web_response={
            "items": [
                {
                    "title": "IU official profile web page",
                    "description": "profile image",
                    "link": "https://example.test/web-profile",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "1",
            "COPYRIGHTER_AUTO_NAVER_BLOG_QUERY_LIMIT": "1",
            "COPYRIGHTER_AUTO_NAVER_WEB_QUERY_LIMIT": "1",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content

    def fetcher(url: str) -> bytes:
        if url == "https://example.test/web-profile":
            return b'<meta property="og:image" content="https://example.test/web-image.png">'
        if url == "https://example.test/web-image.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    naver_calls = [call["url"] for call in transport.calls if "openapi.naver.com/v1/search" in call["url"]]
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
        and item["remoteImageUrl"] == "https://example.test/web-image.png"
    )

    assert any("/v1/search/image" in url for url in naver_calls)
    assert any("/v1/search/blog" in url for url in naver_calls)
    assert any("/v1/search/webkr" in url for url in naver_calls)
    assert any(
        item["source"] == "naver"
        and item["url"] == "https://example.test/web-profile"
        and item["searchType"] == "web"
        and item["matchType"] == "page"
        for item in review["evidence"]
    )
    assert any(item["provider"] == "naver_web" for item in review["queryHistory"])
    assert page_image_match["sourcePageUrl"] == "https://example.test/web-profile"
    assert page_image_match["imageCandidateSource"] == "html_page_image"
    assert review["riskBand"] == "high"


def test_sqlite_store_auto_runs_google_custom_image_search_from_google_entities(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                    }
                }
            ]
        },
        naver_response={"items": []},
        google_custom_image_response={
            "items": [
                {
                    "title": "IU official profile",
                    "link": "https://example.test/google-custom-iu.png",
                    "displayLink": "example.test",
                    "snippet": "profile image",
                    "image": {
                        "thumbnailLink": "https://example.test/google-custom-iu-thumb.png",
                        "contextLink": "https://example.test/google-custom-profile",
                    },
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
            "COPYRIGHTER_AUTO_GOOGLE_CUSTOM_QUERY_LIMIT": "1",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "0",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=lambda url: submitted_image,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    custom_calls = [call["url"] for call in transport.calls if "www.googleapis.com/customsearch/v1" in call["url"]]
    remote_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_image"
        and item["remoteImageUrl"] == "https://example.test/google-custom-iu.png"
    )

    assert len(custom_calls) == 2
    assert "q=IU" in custom_calls[0]
    assert "searchType=image" in custom_calls[0]
    assert "searchType=image" not in custom_calls[1]
    assert "key=google-search-key" in custom_calls[0]
    assert "cx=search-engine-id" in custom_calls[0]
    assert any(
        item["source"] == "google"
        and item["domain"] == "google_custom_search"
        and item["searchType"] == "image"
        and item["url"] == "https://example.test/google-custom-profile"
        for item in review["evidence"]
    )
    assert any(item["provider"] == "google_search" for item in review["queryHistory"])
    assert remote_match["sourcePageUrl"] == "https://example.test/google-custom-profile"
    assert review["providerState"]["google_search"] == "covered"
    assert review["riskBand"] == "high"


def test_sqlite_store_falls_back_to_google_custom_web_page_images_when_image_search_is_empty(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                    }
                }
            ]
        },
        naver_response={"items": []},
        google_custom_image_response={"items": []},
        google_custom_web_response={
            "items": [
                {
                    "title": "IU profile page",
                    "link": "https://example.test/google-custom-web-profile",
                    "displayLink": "example.test",
                    "snippet": "profile image",
                    "pagemap": {
                        "cse_image": [{"src": "https://example.test/google-custom-web-image.png"}],
                    },
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
            "COPYRIGHTER_AUTO_GOOGLE_CUSTOM_QUERY_LIMIT": "1",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "0",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=lambda url: submitted_image,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    custom_calls = [call["url"] for call in transport.calls if "www.googleapis.com/customsearch/v1" in call["url"]]
    remote_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
        and item["remoteImageUrl"] == "https://example.test/google-custom-web-image.png"
    )

    assert len(custom_calls) == 2
    assert "searchType=image" in custom_calls[0]
    assert "searchType=image" not in custom_calls[1]
    assert any(
        item["source"] == "google"
        and item["domain"] == "google_custom_search"
        and item["searchType"] == "web"
        and item["url"] == "https://example.test/google-custom-web-profile"
        for item in review["evidence"]
    )
    assert remote_match["sourcePageUrl"] == "https://example.test/google-custom-web-profile"
    assert review["riskBand"] == "high"


def test_sqlite_store_compares_relative_google_custom_pagemap_images_against_submission(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                    }
                }
            ]
        },
        naver_response={"items": []},
        google_custom_image_response={"items": []},
        google_custom_web_response={
            "items": [
                {
                    "title": "IU profile page",
                    "link": "https://example.test/profiles/iu",
                    "displayLink": "example.test",
                    "snippet": "profile image",
                    "pagemap": {
                        "cse_image": [{"src": "/media/google-custom-web-image.png"}],
                    },
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
            "COPYRIGHTER_AUTO_GOOGLE_CUSTOM_QUERY_LIMIT": "1",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "0",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/media/google-custom-web-image.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    remote_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
        and item["remoteImageUrl"] == "https://example.test/media/google-custom-web-image.png"
    )

    assert fetched_urls == ["https://example.test/media/google-custom-web-image.png"]
    assert remote_match["sourcePageUrl"] == "https://example.test/profiles/iu"
    assert review["riskBand"] == "high"


def test_sqlite_store_falls_back_to_google_custom_web_when_image_results_do_not_match_submission(tmp_path: Path):
    from PIL import Image, ImageDraw

    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                    }
                }
            ]
        },
        naver_response={"items": []},
        google_custom_image_response={
            "items": [
                {
                    "title": "Different IU result",
                    "link": "https://example.test/wrong-result.png",
                    "displayLink": "example.test",
                    "snippet": "wrong image",
                    "image": {
                        "thumbnailLink": "https://example.test/wrong-thumb.png",
                        "contextLink": "https://example.test/wrong-profile",
                    },
                }
            ]
        },
        google_custom_web_response={
            "items": [
                {
                    "title": "IU profile page",
                    "link": "https://example.test/google-custom-web-profile",
                    "displayLink": "example.test",
                    "snippet": "profile image",
                    "pagemap": {
                        "cse_image": [{"src": "https://example.test/google-custom-web-image.png"}],
                    },
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
            "COPYRIGHTER_AUTO_GOOGLE_CUSTOM_QUERY_LIMIT": "1",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "0",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    different = BytesIO()
    different_image = Image.new("RGB", (160, 120), color=(20, 30, 40))
    ImageDraw.Draw(different_image).rectangle((0, 0, 80, 120), fill=(255, 255, 255))
    ImageDraw.Draw(different_image).line((0, 0, 159, 119), fill=(255, 0, 0), width=8)
    different_image.save(different, format="PNG")

    def fetcher(url: str) -> bytes:
        if url in {"https://example.test/wrong-result.png", "https://example.test/wrong-thumb.png"}:
            return different.getvalue()
        if url == "https://example.test/google-custom-web-image.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    custom_calls = [call["url"] for call in transport.calls if "www.googleapis.com/customsearch/v1" in call["url"]]
    remote_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
        and item["remoteImageUrl"] == "https://example.test/google-custom-web-image.png"
    )

    assert len(custom_calls) == 2
    assert "searchType=image" in custom_calls[0]
    assert "searchType=image" not in custom_calls[1]
    assert remote_match["sourcePageUrl"] == "https://example.test/google-custom-web-profile"
    assert review["riskBand"] == "high"


def test_sqlite_store_falls_back_to_naver_blog_when_image_results_do_not_match_submission(tmp_path: Path):
    from PIL import Image, ImageDraw

    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "IU", "score": 0.91}],
                    }
                }
            ]
        },
        naver_response={
            "items": [
                {
                    "title": "Wrong IU image result",
                    "link": "https://example.test/wrong-naver.png",
                    "thumbnail": "https://example.test/wrong-naver-thumb.png",
                    "page_url": "https://example.test/wrong-naver-page",
                }
            ]
        },
        naver_blog_response={
            "items": [
                {
                    "title": "IU official profile blog",
                    "description": "profile image",
                    "link": "https://example.test/blog-post",
                    "bloggername": "official blog",
                    "bloggerlink": "https://blog.example.test",
                    "postdate": "20260527",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "1",
            "COPYRIGHTER_AUTO_NAVER_BLOG_QUERY_LIMIT": "1",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    different = BytesIO()
    different_image = Image.new("RGB", (160, 120), color=(20, 30, 40))
    ImageDraw.Draw(different_image).rectangle((0, 0, 80, 120), fill=(255, 255, 255))
    ImageDraw.Draw(different_image).line((0, 0, 159, 119), fill=(255, 0, 0), width=8)
    different_image.save(different, format="PNG")

    def fetcher(url: str) -> bytes:
        if url in {"https://example.test/wrong-naver.png", "https://example.test/wrong-naver-thumb.png"}:
            return different.getvalue()
        if url == "https://example.test/blog-post":
            return b'<meta property="og:image" content="https://example.test/blog-image.png">'
        if url == "https://example.test/blog-image.png":
            return submitted_image
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()

    store.seed_from_image_store(image_store)
    review = store.review("SUB-PNG1")
    naver_calls = [call["url"] for call in transport.calls if "openapi.naver.com/v1/search" in call["url"]]
    page_image_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item["matchType"] == "search_result_page_image"
        and item["remoteImageUrl"] == "https://example.test/blog-image.png"
    )

    assert any("/v1/search/image" in url for url in naver_calls)
    assert any("/v1/search/blog" in url for url in naver_calls)
    assert page_image_match["sourcePageUrl"] == "https://example.test/blog-post"
    assert review["riskBand"] == "high"


def test_sqlite_store_rerun_enrichment_uses_ollama_llm_when_configured(tmp_path: Path):
    transport = FakeJsonTransport({"response": "Source-linked review summary"})
    runtime = build_provider_runtime(
        env={"OLLAMA_BASE_URL": "http://localhost:11434", "OLLAMA_MODEL": "qwen2.5:0.5b-instruct"},
        transport=transport,
    )
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    review = store.rerun_enrichment("SUB-DB1")

    assert any("localhost:11434/api/generate" in call["url"] for call in transport.calls)
    assert any(item["source"] == "llm" and item["title"] == "Assistant summarized source-linked evidence" for item in review["evidence"])


def test_sqlite_store_seed_auto_runs_ollama_llm_when_source_evidence_exists(tmp_path: Path):
    transport = FakeJsonTransport({"response": "Seed-time source summary"})
    runtime = build_provider_runtime(
        env={"OLLAMA_BASE_URL": "http://localhost:11434", "OLLAMA_MODEL": "qwen2.5:0.5b-instruct"},
        transport=transport,
    )
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    review = store.review("SUB-DB1")
    llm_coverage = next(
        item for item in store.bootstrap()["searchCoverage"]["providers"] if item["id"] == "llm"
    )

    assert any("localhost:11434/api/generate" in call["url"] for call in transport.calls)
    assert any(item["source"] == "llm" and item["title"] == "Assistant summarized source-linked evidence" for item in review["evidence"])
    assert review["providerState"]["llm"] == "covered"
    assert llm_coverage["queryEntries"] == 1
    assert llm_coverage["evidenceSubmissions"] == 1


def test_sqlite_store_rerun_enrichment_refreshes_google_image_search_results(tmp_path: Path):
    transport = FakeJsonTransport(
        {
            "responses": [
                {
                    "webDetection": {
                        "visuallySimilarImages": [{"url": "https://example.test/similar.jpg", "score": 0.68}],
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "score": 0.82,
                                "pageTitle": "Celebrity image result",
                            }
                        ],
                    }
                }
            ]
        }
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _image_store(tmp_path / "submissions")
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()
    store.seed_from_image_store(image_store)
    calls_after_seed = len(transport.calls)

    review = store.rerun_enrichment("SUB-DB1", image_store)

    assert len(transport.calls) == calls_after_seed + 1
    assert any(item["source"] == "google" and item["matchType"] == "visual" for item in review["evidence"])
    assert any(item["source"] == "google" and item["pageTitle"] == "Celebrity image result" for item in review["evidence"])


def test_sqlite_store_rerun_enrichment_runs_google_web_detection_on_face_crop_when_enabled(
    tmp_path: Path,
    monkeypatch,
):
    class OneFaceDetector:
        def detect(self, image):
            return FacePersonSignal(
                face_count=1,
                person_count=1,
                face_boxes=((20, 20, 80, 80),),
            )

    transport = FakeJsonTransport(
        {
            "responses": [
                {
                    "webDetection": {
                        "webEntities": [{"description": "Known actor", "score": 0.77}],
                    }
                }
            ]
        }
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "COPYRIGHTER_GOOGLE_FACE_CROP_SEARCH": "true",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()
    monkeypatch.setattr(sqlite_store_module, "HeuristicFacePersonDetector", lambda: OneFaceDetector())
    store.seed_from_image_store(image_store)
    calls_after_seed = len(transport.calls)

    review = store.rerun_enrichment("SUB-PNG1", image_store)

    assert len(transport.calls) == calls_after_seed + 2
    face_crop_evidence = [
        item for item in review["evidence"] if item["source"] == "google" and item["faceCropSearch"]
    ]
    assert face_crop_evidence
    assert face_crop_evidence[0]["group"] == "face_web"
    assert face_crop_evidence[0]["contributed"] is False
    assert face_crop_evidence[0]["cropIndex"] == 1
    assert "identity" not in face_crop_evidence[0]


def test_sqlite_store_compares_face_crop_search_result_images_against_crop_fingerprint(
    tmp_path: Path,
    monkeypatch,
):
    class BoxedFaceDetector:
        def detect(self, image):
            return FacePersonSignal(
                face_count=1,
                person_count=1,
                face_boxes=((150, 80, 80, 80),),
            )

    empty_google = {"responses": [{"webDetection": {}}]}
    crop_google = {
        "responses": [
            {
                "webDetection": {
                    "fullMatchingImages": [
                        {
                            "url": "https://example.test/face-crop-match.jpg",
                            "score": 0.93,
                        }
                    ]
                }
            }
        ]
    }
    transport = SequencedRoutingFakeJsonTransport(
        google_responses=[empty_google, empty_google, crop_google],
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "COPYRIGHTER_GOOGLE_FACE_CROP_SEARCH": "true",
            "COPYRIGHTER_AUTO_GOOGLE_CUSTOM_QUERY_LIMIT": "0",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "0",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _face_crop_photo_store(tmp_path / "submissions")
    original = image_store.image_payload("SUB-FACE1")
    remote_crop = build_face_crop_derivatives(
        ImagePayload(original.content, original.width, original.height, original.metadata),
        [(150, 80, 80, 80)],
    )[0].content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/face-crop-match.jpg":
            return remote_crop
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    monkeypatch.setattr(sqlite_store_module, "HeuristicFacePersonDetector", lambda: BoxedFaceDetector())
    store.seed_from_image_store(image_store)

    review = store.rerun_enrichment("SUB-FACE1", image_store)
    face_crop_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item.get("faceCropSearch")
        and item["remoteImageUrl"] == "https://example.test/face-crop-match.jpg"
    )

    assert fetched_urls == ["https://example.test/face-crop-match.jpg"]
    assert face_crop_match["group"] == "face_web"
    assert face_crop_match["matchType"] == "face_crop_search_result_image"
    assert face_crop_match["imageCandidateSource"] == "face_crop_result_image_url"
    assert face_crop_match["contributed"] is False
    assert face_crop_match["cropIndex"] == 1
    assert face_crop_match["similarity"] >= 0.9


def test_sqlite_store_compares_face_crop_search_result_page_images_against_crop_fingerprint(
    tmp_path: Path,
    monkeypatch,
):
    class BoxedFaceDetector:
        def detect(self, image):
            return FacePersonSignal(
                face_count=1,
                person_count=1,
                face_boxes=((150, 80, 80, 80),),
            )

    empty_google = {"responses": [{"webDetection": {}}]}
    crop_google = {
        "responses": [
            {
                "webDetection": {
                    "pagesWithMatchingImages": [
                        {
                            "url": "https://example.test/face-source-page",
                            "score": 0.88,
                            "pageTitle": "Known actor profile page",
                        }
                    ]
                }
            }
        ]
    }
    transport = SequencedRoutingFakeJsonTransport(
        google_responses=[empty_google, empty_google, crop_google],
        naver_response={"items": []},
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "COPYRIGHTER_GOOGLE_FACE_CROP_SEARCH": "true",
            "COPYRIGHTER_AUTO_GOOGLE_CUSTOM_QUERY_LIMIT": "0",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "0",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _face_crop_photo_store(tmp_path / "submissions")
    original = image_store.image_payload("SUB-FACE1")
    remote_crop = build_face_crop_derivatives(
        ImagePayload(original.content, original.width, original.height, original.metadata),
        [(150, 80, 80, 80)],
    )[0].content
    fetched_urls: list[str] = []

    def fetcher(url: str) -> bytes:
        fetched_urls.append(url)
        if url == "https://example.test/face-source-page":
            return b"""
            <html>
              <head>
                <meta property="og:image" content="/media/face-source-crop.jpg">
              </head>
              <body></body>
            </html>
            """
        if url == "https://example.test/media/face-source-crop.jpg":
            return remote_crop
        raise AssertionError(f"unexpected URL fetched: {url}")

    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=fetcher,
    )
    store.initialize()
    monkeypatch.setattr(sqlite_store_module, "HeuristicFacePersonDetector", lambda: BoxedFaceDetector())
    store.seed_from_image_store(image_store)

    review = store.rerun_enrichment("SUB-FACE1", image_store)
    face_crop_match = next(
        item
        for item in review["evidence"]
        if item["source"] == "fingerprint"
        and item.get("faceCropSearch")
        and item["remoteImageUrl"] == "https://example.test/media/face-source-crop.jpg"
    )

    assert fetched_urls == [
        "https://example.test/face-source-page",
        "https://example.test/media/face-source-crop.jpg",
    ]
    assert face_crop_match["group"] == "face_web"
    assert face_crop_match["matchType"] == "face_crop_search_result_page_image"
    assert face_crop_match["imageCandidateSource"] == "face_crop_html_page_image"
    assert face_crop_match["contributed"] is False
    assert face_crop_match["cropIndex"] == 1
    assert face_crop_match["similarity"] >= 0.9


def test_sqlite_store_rerun_enrichment_uses_face_crop_page_title_for_naver_search(
    tmp_path: Path,
    monkeypatch,
):
    class BoxedFaceDetector:
        def detect(self, image):
            return FacePersonSignal(
                face_count=1,
                person_count=1,
                face_boxes=((20, 20, 80, 80),),
            )

    empty_google = {"responses": [{"webDetection": {}}]}
    crop_google = {
        "responses": [
            {
                "webDetection": {
                    "pagesWithMatchingImages": [
                        {
                            "url": "https://example.test/face-source-page",
                            "score": 0.81,
                            "pageTitle": "Known actor official profile - Example Site",
                        }
                    ]
                }
            }
        ]
    }
    transport = SequencedRoutingFakeJsonTransport(
        google_responses=[empty_google, empty_google, crop_google],
        naver_response={
            "items": [
                {
                    "title": "Known actor official profile",
                    "link": "https://example.test/known-actor.jpg",
                    "thumbnail": "https://example.test/known-actor-thumb.jpg",
                    "page_url": "https://example.test/known-actor-profile",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "COPYRIGHTER_GOOGLE_FACE_CROP_SEARCH": "true",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "1",
            "COPYRIGHTER_AUTO_GOOGLE_CUSTOM_QUERY_LIMIT": "0",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _png_image_store(tmp_path / "submissions")
    submitted_image = image_store.image_payload("SUB-PNG1").content
    store = CopyrighterStore(
        tmp_path / "copyrighter.sqlite3",
        provider_runtime=runtime,
        candidate_image_fetcher=lambda url: submitted_image,
    )
    store.initialize()
    monkeypatch.setattr(sqlite_store_module, "HeuristicFacePersonDetector", lambda: BoxedFaceDetector())
    store.seed_from_image_store(image_store)
    calls_after_seed = len(transport.calls)

    review = store.rerun_enrichment("SUB-PNG1", image_store)
    new_naver_calls = [
        call["url"]
        for call in transport.calls[calls_after_seed:]
        if "openapi.naver.com/v1/search/image" in call["url"]
    ]

    assert len(new_naver_calls) == 1
    assert "query=Known+actor+official+profile" in new_naver_calls[0]
    assert any(
        item["source"] == "google"
        and item["faceCropSearch"]
        and item["pageTitle"] == "Known actor official profile - Example Site"
        and item["contributed"] is False
        for item in review["evidence"]
    )
    assert any(
        item["source"] == "naver"
        and item["queryStrategy"] == "google_face_crop_page"
        and item["url"] == "https://example.test/known-actor-profile"
        for item in review["evidence"]
    )
    assert any(
        item.get("source") == "fingerprint"
        and item.get("matchType") == "search_result_image"
        and item.get("remoteImageUrl") == "https://example.test/known-actor.jpg"
        for item in review["evidence"]
    )


def test_sqlite_store_rerun_enrichment_auto_runs_naver_from_google_page_title(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "score": 0.82,
                                "pageTitle": "김연아 공식 화보",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={
            "items": [
                {
                    "title": "김연아 공식 화보",
                    "link": "https://example.test/yuna.jpg",
                    "thumbnail": "https://example.test/yuna-thumb.jpg",
                    "page_url": "https://example.test/yuna-page",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _image_store(tmp_path / "submissions")
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()
    store.seed_from_image_store(image_store)

    review = store.rerun_enrichment("SUB-DB1", image_store)

    assert any("query=%EA%B9%80%EC%97%B0%EC%95%84" in call["url"] for call in transport.calls)
    assert any(item["source"] == "naver" and item["query"] == "김연아 공식 화보" for item in review["evidence"])


def test_sqlite_store_rerun_enrichment_auto_uses_weak_google_page_and_label_as_queries(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={
            "responses": [
                {
                    "webDetection": {
                        "bestGuessLabels": [{"label": "IU official profile"}],
                        "pagesWithMatchingImages": [
                            {
                                "url": "https://example.test/source-page",
                                "score": 0.82,
                                "pageTitle": "Known actor official profile - Example Site",
                            }
                        ],
                    }
                }
            ]
        },
        naver_response={
            "items": [
                {
                    "title": "IU official profile photo",
                    "link": "https://example.test/iu-official.jpg",
                    "thumbnail": "https://example.test/iu-official-thumb.jpg",
                    "page_url": "https://example.test/iu-page",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "GOOGLE_CLOUD_VISION_API_KEY": "google-key",
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "4",
            "COPYRIGHTER_SEARCH_RESULT_COMPARE_LIMIT": "0",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    image_store = _image_store(tmp_path / "submissions")
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()
    store.seed_from_image_store(image_store)

    review = store.rerun_enrichment("SUB-DB1", image_store)

    naver_queries = [
        call["url"]
        for call in transport.calls
        if "openapi.naver.com/v1/search/image" in call["url"]
    ]

    assert any("query=Known+actor+official+profile" in url for url in naver_queries)
    assert any("query=IU+official+profile" in url for url in naver_queries)
    assert any(
        item["source"] == "naver"
        and item["query"] == "Known actor official profile"
        for item in review["evidence"]
    )
    assert any(
        item["source"] == "naver"
        and item["query"] == "IU official profile"
        for item in review["evidence"]
    )
    assert any(
        item["source"] == "naver"
        and item.get("queryStrategy") == "google_page"
        for item in review["evidence"]
    )
    assert any(
        item["source"] == "naver"
        and item.get("queryStrategy") == "google_best_guess"
        for item in review["evidence"]
    )


def test_sqlite_store_migrates_google_best_guess_labels_to_non_scoring_hints(tmp_path: Path):
    transport = FakeJsonTransport(
        {
            "responses": [
                {
                    "webDetection": {
                        "bestGuessLabels": [{"label": "gentleman"}],
                    }
                }
            ]
        }
    )
    runtime = build_provider_runtime(
        env={"GOOGLE_CLOUD_VISION_API_KEY": "google-key"},
        transport=transport,
    )
    db_path = tmp_path / "copyrighter.sqlite3"
    store = CopyrighterStore(db_path, provider_runtime=runtime)
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))

    review = store.review("SUB-DB1")

    weak_label = next(item for item in review["evidence"] if item["source"] == "google")

    assert review["riskScore"] == 35
    assert review["riskBand"] == "medium"
    assert review["reasons"] == ["Face/person detected"]
    assert weak_label["title"] == "Google weak label gentleman"
    assert weak_label["contributed"] is False


def test_sqlite_store_rescores_existing_saved_google_best_guess_labels(tmp_path: Path):
    db_path = tmp_path / "copyrighter.sqlite3"
    image_store = _image_store(tmp_path / "submissions")
    store = CopyrighterStore(db_path)
    store.initialize()
    store.seed_from_image_store(image_store)
    submission = store.review("SUB-DB1")
    submission["riskScore"] = 35
    submission["riskBand"] = "medium"
    submission["reasons"] = ["Face/person detected", "Best guess label gentleman"]
    store._put("submissions", "SUB-DB1", submission)
    store._put(
        "evidence",
        "ev-old-google-label",
        {
            "id": "ev-old-google-label",
            "group": "google",
            "source": "google",
            "title": "Best guess label gentleman",
            "confidence": 0.6,
            "query": "",
            "domain": "internal",
            "url": "",
            "retrievedAt": "2026-05-26 12:53:12",
            "contributed": True,
            "sourceEvidenceIds": [],
            "status": "active",
            "submission_id": "SUB-DB1",
        },
    )

    reopened = CopyrighterStore(db_path)
    reopened.initialize()
    reopened.seed_from_image_store(image_store)
    review = reopened.review("SUB-DB1")
    weak_label = next(item for item in review["evidence"] if item["id"] == "ev-old-google-label")

    assert review["riskScore"] == 35
    assert review["riskBand"] == "medium"
    assert "Face/person detected" in review["reasons"]
    assert "Best guess label gentleman" not in review["reasons"]
    assert weak_label["title"] == "Google weak label gentleman"
    assert weak_label["contributed"] is False


def test_sqlite_store_runs_auto_search_for_existing_submission(tmp_path: Path):
    transport = RoutingFakeJsonTransport(
        google_response={"response": "ignored"},
        naver_response={
            "items": [
                {
                    "title": "IU official profile",
                    "link": "https://example.test/naver-image.jpg",
                    "thumbnail": "https://example.test/naver-thumb.jpg",
                    "page_url": "https://example.test/naver-page",
                }
            ]
        },
        google_custom_image_response={
            "items": [
                {
                    "title": "IU official profile",
                    "link": "https://example.test/custom-image.jpg",
                    "displayLink": "example.test",
                    "snippet": "official profile image",
                    "image": {
                        "thumbnailLink": "https://example.test/custom-thumb.jpg",
                        "contextLink": "https://example.test/custom-page",
                    },
                }
            ]
        },
        google_custom_web_response={
            "items": [
                {
                    "title": "IU official profile page",
                    "link": "https://example.test/custom-page-image.jpg",
                    "displayLink": "example.test",
                    "snippet": "official profile page",
                }
            ]
        },
    )
    runtime = build_provider_runtime(
        env={
            "NAVER_CLIENT_ID": "naver-id",
            "NAVER_CLIENT_SECRET": "naver-secret",
            "GOOGLE_CUSTOM_SEARCH_API_KEY": "google-search-key",
            "GOOGLE_CUSTOM_SEARCH_CX": "search-engine-id",
            "COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT": "0",
            "COPYRIGHTER_AUTO_GOOGLE_CUSTOM_QUERY_LIMIT": "0",
            "COPYRIGHTER_AUTO_NAVER_BLOG_QUERY_LIMIT": "0",
            "COPYRIGHTER_AUTO_NAVER_WEB_QUERY_LIMIT": "0",
            "COPYRIGHTER_SEARCH_RESULT_COMPARE_LIMIT": "0",
        },
        transport=transport,
    )
    runtime.llm_assistant = None
    store = CopyrighterStore(tmp_path / "copyrighter.sqlite3", provider_runtime=runtime)
    store.initialize()
    store.seed_from_image_store(_image_store(tmp_path / "submissions"))
    submission = store._get("submissions", "SUB-DB1")
    submission["title"] = "IU official profile"
    store._put("submissions", "SUB-DB1", submission)

    runtime.auto_naver_query_limit = 1
    runtime.auto_google_custom_query_limit = 1

    calls_before = len(transport.calls)
    review = store.run_auto_search("SUB-DB1")
    auto_calls = transport.calls[calls_before:]

    assert any(
        "openapi.naver.com/v1/search/image" in call["url"]
        for call in auto_calls
    )
    assert any(
        "www.googleapis.com/customsearch/v1" in call["url"]
        and "searchType=image" in call["url"]
        for call in auto_calls
    )

    providers = {item["id"]: item for item in store.providers()}
    assert providers["naver"]["usage"] == 1
    assert providers["google_search"]["usage"] >= 1
    assert any(item["source"] == "naver" for item in review["evidence"])
    assert any(
        item["source"] == "google" and item.get("domain") == "google_custom_search"
        for item in review["evidence"]
    )
    history_providers = {item["provider"] for item in review["queryHistory"]}
    assert "naver" in history_providers
    assert "google_search" in history_providers


def test_connect_uses_wal_and_busy_timeout(tmp_path: Path):
    store = CopyrighterStore(tmp_path / "c.sqlite3")
    store.initialize()
    with store._connect() as conn:
        assert conn.execute("pragma journal_mode").fetchone()[0].lower() == "wal"
        assert int(conn.execute("pragma busy_timeout").fetchone()[0]) >= 5000


def test_record_decision_rolls_back_when_audit_fails(tmp_path: Path, monkeypatch):
    store = CopyrighterStore(tmp_path / "c.sqlite3")
    store.initialize()
    store._put(
        "submissions",
        "SUB-1",
        {
            "id": "SUB-1",
            "title": "t",
            "asset": "",
            "riskScore": 0,
            "riskBand": "low",
            "decisionStatus": "unreviewed",
            "providerState": {},
            "fileFacts": {},
            "evidence": [],
        },
    )

    def boom(*args, **kwargs):
        raise RuntimeError("audit down")

    monkeypatch.setattr(store, "add_audit_event", boom)
    with pytest.raises(RuntimeError):
        store.record_decision("SUB-1", "held", "memo")

    # The status change must roll back atomically with the failed audit write.
    assert store._get("submissions", "SUB-1")["decisionStatus"] == "unreviewed"
    with pytest.raises(KeyError):
        store._get("knowledge_entries", sqlite_store_module._stable_id("kb-watchlist", "SUB-1"))


@pytest.mark.parametrize(
    "url",
    [
        "http://127.0.0.1/x",
        "http://169.254.169.254/latest/meta-data",
        "http://10.0.0.1/a",
        "http://192.168.1.1/a",
        "http://[::1]/a",
        "http://localhost/a",
        "ftp://example.test/a",
    ],
)
def test_fetch_rejects_internal_or_non_http_hosts(url):
    # SSRF guard runs before any network call, so these raise without a fetch.
    with pytest.raises(ValueError):
        sqlite_store_module._fetch_url_bytes(url)


@pytest.mark.parametrize(
    "method_name",
    ["knowledge_media_path", "collected_media_path", "face_crop_media_path"],
)
@pytest.mark.parametrize("evil", ["../../etc/passwd", "../../../secret", "../escape.txt"])
def test_media_path_guards_reject_traversal(tmp_path: Path, method_name: str, evil: str):
    store = CopyrighterStore(tmp_path / "c.sqlite3")
    store.initialize()
    method = getattr(store, method_name)
    with pytest.raises(ValueError):
        method(evil)


def test_promote_collection_candidate_is_idempotent(tmp_path: Path):
    store = CopyrighterStore(tmp_path / "c.sqlite3")
    store.initialize()
    store._put(
        "collection_candidates",
        "CAND-1",
        {
            "id": "CAND-1",
            "provider": "naver",
            "query": "iu",
            "title": "IU",
            "status": "candidate",
            "sourceUrl": "",
            "collectedEpoch": 0,
            "sampleFingerprints": [],
        },
    )

    store.promote_collection_candidate("CAND-1", {})
    store.promote_collection_candidate("CAND-1", {})  # double-click / retry

    candidate_entries = [
        entry for entry in store._all("knowledge_entries") if entry.get("sourceCandidate") == "CAND-1"
    ]
    assert len(candidate_entries) == 1
    assert store._get("collection_candidates", "CAND-1")["status"] == "promoted"


def test_constraint_migration_failure_preserves_data_and_cleans_temp(tmp_path: Path):
    db_path = tmp_path / "legacy.sqlite3"
    legacy = sqlite3.connect(db_path)
    # Legacy submissions table without the new risk_band / json_valid constraints.
    legacy.execute(
        "create table submissions (id text primary key, title text, risk_score integer, "
        "risk_band text, decision_status text, submitted_epoch integer, queue_id text, payload text)"
    )
    # A value the NEW CHECK rejects, so the rebuild copy step raises.
    legacy.execute(
        "insert into submissions values ('S1', 't', 0, 'weird', 'unreviewed', 0, '', '{}')"
    )
    legacy.commit()
    legacy.close()

    store = CopyrighterStore(db_path)
    with pytest.raises(sqlite3.IntegrityError):
        store.initialize()

    check = sqlite3.connect(db_path)
    try:
        # Original data survives (copy failed before the original was dropped).
        assert check.execute("select count(*) from submissions").fetchone()[0] == 1
        # No orphaned rebuild temp table is left behind.
        orphan = check.execute(
            "select count(*) from sqlite_master where name like '__copyrighter_%_new'"
        ).fetchone()[0]
        assert orphan == 0
    finally:
        check.close()


def test_purge_expired_face_crops_removes_old_biometric_data(tmp_path: Path):
    import os
    import time

    store = CopyrighterStore(tmp_path / "c.sqlite3")
    store.initialize()
    store.face_crop_retention_days = 30

    crop_dir = store.face_crop_image_dir / "SUB-1"
    crop_dir.mkdir(parents=True)
    crop_file = crop_dir / "crop-1.jpg"
    crop_file.write_bytes(b"jpegdata")
    aged = time.time() - 60 * 86400
    os.utime(crop_file, (aged, aged))

    store._put(
        "submissions",
        "SUB-1",
        {
            "id": "SUB-1",
            "title": "t",
            "asset": "",
            "riskScore": 0,
            "riskBand": "low",
            "decisionStatus": "unreviewed",
            "providerState": {},
            "fileFacts": {},
            "evidence": [],
            "faceCrops": [{"index": 1, "url": "/face-crop-media/SUB-1/crop-1.jpg", "box": [0, 0, 1, 1]}],
        },
    )

    purged = store.purge_expired_face_crops()

    assert purged == 1
    assert not crop_file.exists()
    assert store._get("submissions", "SUB-1")["faceCrops"] == []
    assert any(event["event"] == "Face crop purged" for event in store.audit_events())


def test_purge_expired_face_crops_keeps_recent_crops(tmp_path: Path):
    store = CopyrighterStore(tmp_path / "c.sqlite3")
    store.initialize()
    store.face_crop_retention_days = 30

    crop_dir = store.face_crop_image_dir / "SUB-2"
    crop_dir.mkdir(parents=True)
    crop_file = crop_dir / "crop-1.jpg"
    crop_file.write_bytes(b"jpegdata")  # fresh mtime

    assert store.purge_expired_face_crops() == 0
    assert crop_file.exists()
