from __future__ import annotations

import html
import re
from dataclasses import dataclass

from rights_filter.domain.records import Evidence, EvidenceSource, KnowledgeBaseEntry


@dataclass(frozen=True)
class GeneratedSearchQuery:
    query: str
    strategy: str
    source: str
    priority: int


class SearchQueryGenerator:
    def plan(
        self,
        evidence: list[Evidence],
        knowledge_entries: list[KnowledgeBaseEntry],
        max_queries: int = 5,
    ) -> list[GeneratedSearchQuery]:
        candidates: list[GeneratedSearchQuery] = []

        for item in evidence:
            if item.data.get("local_query_hint"):
                candidates.extend(_local_metadata_candidates(item))
                continue
            if item.source != EvidenceSource.WEB_DETECTION:
                continue
            if item.data.get("weak_hint"):
                candidates.extend(_weak_hint_candidates(item))
                continue

            page_title = item.data.get("page_title")
            if page_title:
                candidates.extend(_page_title_candidates(str(page_title)))

            entity = item.data.get("entity") or item.data.get("label")
            if entity:
                candidates.extend(_entity_candidates(str(entity), item.data.get("category")))

        for entry in knowledge_entries:
            keyword = entry.related_keywords[0] if entry.related_keywords else "공식 이미지"
            candidates.append(
                _candidate(f"{entry.name} {keyword}", "knowledge_entry", entry.name, 70)
            )
            for alias in entry.aliases:
                if alias != entry.name:
                    candidates.append(
                        _candidate(f"{alias} {keyword}", "knowledge_alias", entry.name, 68)
                    )

        return _unique_sorted(candidates)[: max(0, max_queries)]

    def generate(
        self,
        evidence: list[Evidence],
        knowledge_entries: list[KnowledgeBaseEntry],
        max_queries: int = 5,
    ) -> list[str]:
        return [
            item.query
            for item in self.plan(
                evidence,
                knowledge_entries,
                max_queries=max_queries,
            )
        ]


def _page_title_candidates(title: str) -> list[GeneratedSearchQuery]:
    cleaned = _clean_title(title)
    if not cleaned:
        return []

    candidates = [_candidate(cleaned, "google_page", cleaned, 100)]
    if not _has_image_word(cleaned):
        candidates.append(_candidate(f"{cleaned} {_image_query_word(cleaned)}", "google_page", cleaned, 96))
    return candidates


def _weak_hint_candidates(item: Evidence) -> list[GeneratedSearchQuery]:
    if item.data.get("face_crop_search"):
        return _face_crop_candidates(item)

    candidates: list[GeneratedSearchQuery] = []
    page_title = item.data.get("page_title")
    if page_title:
        candidates.extend(_weak_page_title_candidates(str(page_title)))

    label = item.data.get("label")
    entity = item.data.get("entity") or item.data.get("label")
    if entity and (
        not label
        or _is_generic_best_guess_label(str(label))
    ):
        candidates.extend(_weak_entity_candidates(str(entity)))

    if label:
        candidates.extend(_best_guess_label_candidates(str(label)))

    return candidates


def _weak_page_title_candidates(title: str) -> list[GeneratedSearchQuery]:
    cleaned = _clean_title(title)
    if not cleaned:
        return []

    candidates = [_candidate(cleaned, "google_page", cleaned, 88)]
    if not _has_image_word(cleaned):
        candidates.append(
            _candidate(f"{cleaned} {_image_query_word(cleaned)}", "google_page", cleaned, 84)
        )
    return candidates


def _entity_candidates(entity: str, category: object) -> list[GeneratedSearchQuery]:
    cleaned = _clean_query(entity)
    if not cleaned:
        return []

    if _is_person_category(str(category or "")):
        templates = [
            ("{entity} 공식 프로필", 90),
            ("{entity} 사진", 86),
            ("{entity} 화보", 84),
        ]
    else:
        templates = [
            ("{entity} 공식 이미지", 88),
            ("{entity} 이미지", 82),
        ]
    return [
        _candidate(template.format(entity=cleaned), "google_entity", cleaned, priority)
        for template, priority in templates
    ]


def _best_guess_label_candidates(label: str) -> list[GeneratedSearchQuery]:
    cleaned = _clean_query(label)
    if not cleaned or _is_generic_best_guess_label(cleaned):
        return []

    candidates = [_candidate(cleaned, "google_best_guess", cleaned, 60)]
    if not _has_image_word(cleaned):
        candidates.append(_candidate(f"{cleaned} {_image_query_word(cleaned)}", "google_best_guess", cleaned, 56))
    return candidates


def _weak_entity_candidates(entity: str) -> list[GeneratedSearchQuery]:
    cleaned = _clean_query(entity)
    if not cleaned or _is_generic_best_guess_label(cleaned):
        return []

    candidates: list[GeneratedSearchQuery] = [_candidate(cleaned, "google_entity", cleaned, 60)]
    if not _has_image_word(cleaned):
        candidates.append(_candidate(f"{cleaned} image", "google_entity", cleaned, 62))
    return candidates


def _face_crop_candidates(item: Evidence) -> list[GeneratedSearchQuery]:
    candidates: list[GeneratedSearchQuery] = []
    page_title = item.data.get("page_title")
    if page_title:
        candidates.extend(_face_crop_page_title_candidates(str(page_title)))

    entity = item.data.get("entity") or item.data.get("label")
    if entity:
        candidates.extend(_face_crop_entity_candidates(str(entity)))
    return candidates


def _face_crop_page_title_candidates(title: str) -> list[GeneratedSearchQuery]:
    cleaned = _clean_title(title)
    if not cleaned:
        return []

    candidates = [_candidate(cleaned, "google_face_crop_page", cleaned, 74)]
    if not _has_image_word(cleaned):
        candidates.append(_candidate(f"{cleaned} {_image_query_word(cleaned)}", "google_face_crop_page", cleaned, 70))
    return candidates


def _face_crop_entity_candidates(entity: str) -> list[GeneratedSearchQuery]:
    cleaned = _clean_query(entity)
    if not cleaned or _is_generic_best_guess_label(cleaned):
        return []

    candidates = [_candidate(cleaned, "google_face_crop_entity", cleaned, 66)]
    if not _has_image_word(cleaned):
        candidates.append(_candidate(f"{cleaned} {_image_query_word(cleaned)}", "google_face_crop_entity", cleaned, 62))
    return candidates


def _local_metadata_candidates(item: Evidence) -> list[GeneratedSearchQuery]:
    query = _clean_local_query(str(item.data.get("query", "")))
    if not query or _is_generic_local_query(query):
        return []

    source = str(item.data.get("hint_source", "local")).strip() or "local"
    candidates = [_candidate(query, "local_metadata", source, 54)]
    if not _has_image_word(query):
        candidates.append(_candidate(f"{query} {_image_query_word(query)}", "local_metadata", source, 50))
    return candidates


def _candidate(query: str, strategy: str, source: str, priority: int) -> GeneratedSearchQuery:
    return GeneratedSearchQuery(
        query=_trim_query(_clean_query(query)),
        strategy=strategy,
        source=_trim_query(_clean_query(source)),
        priority=priority,
    )


def _unique_sorted(candidates: list[GeneratedSearchQuery]) -> list[GeneratedSearchQuery]:
    result: list[GeneratedSearchQuery] = []
    seen: set[str] = set()
    for candidate in sorted(candidates, key=lambda item: item.priority, reverse=True):
        if not candidate.query:
            continue
        key = _query_key(candidate.query)
        if key in seen:
            continue
        seen.add(key)
        result.append(candidate)
    return result


def _clean_title(value: str) -> str:
    cleaned = _clean_query(value)
    parts = re.split(r"\s(?:-|[|]|–|—|:)\s", cleaned, maxsplit=1)
    return parts[0].strip()


def _clean_query(value: str) -> str:
    text = html.unescape(str(value))
    text = re.sub(r"<[^>]+>", " ", text)
    text = re.sub(r"\s+", " ", text).strip()
    return text


def _clean_local_query(value: str) -> str:
    text = _clean_query(value)
    text = re.sub(r"\.[a-zA-Z0-9]{2,5}$", "", text)
    text = re.sub(r"[_]+", " ", text)
    text = re.sub(r"\s+", " ", text).strip()
    return text


def _trim_query(value: str, limit: int = 80) -> str:
    if len(value) <= limit:
        return value
    trimmed = value[:limit].rsplit(" ", 1)[0].strip()
    return trimmed or value[:limit].strip()


def _query_key(value: str) -> str:
    return " ".join(value.casefold().split())


def _is_person_category(category: str) -> bool:
    return category.casefold() in {"celebrity", "person", "public_figure", "actor", "artist"}


def _is_generic_best_guess_label(value: str) -> bool:
    text = _query_key(value)
    if len(text) < 2:
        return True
    generic_labels = {
        "person",
        "people",
        "man",
        "woman",
        "gentleman",
        "lady",
        "girl",
        "boy",
        "portrait",
        "face",
        "photo",
        "image",
        "picture",
        "selfie",
        "screenshot",
        "wallpaper",
    }
    return text in generic_labels


def _is_generic_local_query(value: str) -> bool:
    text = _query_key(value)
    if _is_generic_best_guess_label(text):
        return True
    if "sample" in text:
        return True
    if re.fullmatch(r"(img|image|photo|picture|portrait|screenshot|download|copy|scan|file)[\s_-]*\d*", text):
        return True
    if re.fullmatch(r"(sub|db|api|test|local)[\s_-]*\d*", text):
        return True
    if re.fullmatch(r"\d+", text):
        return True
    return False


def _has_image_word(value: str) -> bool:
    text = value.casefold()
    markers = ("image", "photo", "picture", "사진", "이미지", "화보", "프로필")
    return any(marker in text for marker in markers)


def _image_query_word(value: str) -> str:
    return "이미지" if _has_hangul(value) else "image"


def _has_hangul(value: str) -> bool:
    return any("\uac00" <= character <= "\ud7a3" for character in value)
