"""Pure lookup-value matching. Fuzzy scores never auto-approve."""

from __future__ import annotations

from dataclasses import dataclass, field
from difflib import SequenceMatcher
import re
from typing import Sequence

from db.v2_import.constants import HIGH_SUGGESTION_THRESHOLD, SUGGESTION_THRESHOLD


def normalize_lookup_value(value: object) -> str:
    text = str(value or "").strip()
    text = re.sub(r"\s+", " ", text)
    return text.casefold()


def display_lookup_value(value: object) -> str:
    text = str(value or "").strip()
    return re.sub(r"\s+", " ", text)


def attribute_code_from_header(header: object) -> str:
    text = re.sub(r"[^a-z0-9]+", "_", str(header or "").strip().lower()).strip("_")
    return text


@dataclass(frozen=True)
class CatalogEntity:
    entity_type: str
    id: int
    name: str
    code: str | None = None
    parent_id: int | None = None
    parent_name: str | None = None
    aliases: tuple[str, ...] = ()
    extra: dict = field(default_factory=dict)

    @property
    def context_label(self) -> str:
        if self.parent_name:
            return f"{self.name} — {self.parent_name}"
        return self.name


@dataclass(frozen=True)
class MatchDecision:
    status: str
    target: CatalogEntity | None = None
    suggestions: tuple[CatalogEntity, ...] = ()
    resolution_source: str | None = None

    def suggestion_payloads(self) -> list[dict]:
        return [
            {
                "id": item.id,
                "name": item.name,
                "code": item.code,
                "parent_id": item.parent_id,
                "parent_name": item.parent_name,
                "label": item.context_label,
                "score": round(
                    SequenceMatcher(
                        None,
                        normalize_lookup_value(item.name),
                        normalize_lookup_value(getattr(self, "_source", item.name)),
                    ).ratio(),
                    4,
                )
                if False
                else None,
            }
            for item in self.suggestions
        ]


def classify_match(
    source_value: str,
    candidates: Sequence[CatalogEntity],
    *,
    known_target: CatalogEntity | None = None,
) -> MatchDecision:
    if known_target is not None:
        return MatchDecision(
            status="known_mapping",
            target=known_target,
            resolution_source="dictionary",
        )

    normalized = normalize_lookup_value(source_value)
    if not normalized:
        return MatchDecision(status="unmatched")

    exact: list[CatalogEntity] = []
    scored: list[tuple[float, CatalogEntity]] = []
    for item in candidates:
        names = {normalize_lookup_value(item.name)}
        if item.code:
            names.add(normalize_lookup_value(item.code))
        names.update(normalize_lookup_value(alias) for alias in item.aliases)
        if normalized in names:
            exact.append(item)
            continue
        best = 0.0
        for name in names:
            if not name:
                continue
            best = max(best, SequenceMatcher(None, normalized, name).ratio())
        if best >= SUGGESTION_THRESHOLD:
            scored.append((best, item))

    if len(exact) == 1:
        return MatchDecision(status="exact_match", target=exact[0], resolution_source="exact")
    if len(exact) > 1:
        return MatchDecision(status="ambiguous", suggestions=tuple(exact), resolution_source="exact")

    scored.sort(key=lambda pair: (-pair[0], pair[1].name))
    if not scored:
        return MatchDecision(status="unmatched")
    if len(scored) == 1:
        _score, item = scored[0]
        return MatchDecision(
            status="suggested",
            suggestions=(item,),
            resolution_source="high_fuzzy" if _score >= HIGH_SUGGESTION_THRESHOLD else "fuzzy",
        )
    return MatchDecision(
        status="ambiguous",
        suggestions=tuple(item for _score, item in scored[:8]),
        resolution_source="fuzzy",
    )


def similar_entities(
    source_value: str,
    candidates: Sequence[CatalogEntity],
    *,
    limit: int = 5,
) -> list[dict]:
    normalized = normalize_lookup_value(source_value)
    ranked: list[tuple[float, CatalogEntity]] = []
    for item in candidates:
        score = SequenceMatcher(None, normalized, normalize_lookup_value(item.name)).ratio()
        if item.code:
            score = max(score, SequenceMatcher(None, normalized, normalize_lookup_value(item.code)).ratio())
        if score >= SUGGESTION_THRESHOLD:
            ranked.append((score, item))
    ranked.sort(key=lambda pair: (-pair[0], pair[1].name))
    return [
        {
            "id": item.id,
            "name": item.name,
            "code": item.code,
            "parent_id": item.parent_id,
            "parent_name": item.parent_name,
            "label": item.context_label,
            "score": round(score, 4),
            "confidence": "high" if score >= HIGH_SUGGESTION_THRESHOLD else "likely",
        }
        for score, item in ranked[:limit]
    ]
