"""Deliberately simple fuzzy matching for find_entities_by_description.

No embeddings, no Levenshtein: substring + prefix matching over a normalized
haystack (entity_id, friendly_name, area), plus a small Polish->English
synonym table, because that is what the actual failure mode needs. The
instance is a Polish household whose entity_ids are English-ish and
transliterated (`sensor.thsalon_temperature`, `binary_sensor.mdwejscie_*`),
so an agent asking for "czujnik temperatury salon" has to cross both the
language boundary and the missing-diacritics boundary. Everything here is
explainable, which is why each hit carries the reason it matched.
"""
import re

_PL = str.maketrans(
    {
        "ą": "a",
        "ć": "c",
        "ę": "e",
        "ł": "l",
        "ń": "n",
        "ó": "o",
        "ś": "s",
        "ż": "z",
        "ź": "z",
    }
)

#: Polish query word (normalized) -> extra terms to try against the haystack.
#: Values are matched exactly the same way the original token is.
SYNONYMS = {
    "czujnik": ["sensor"],
    "czujniki": ["sensor"],
    "czujnika": ["sensor"],
    "temperatura": ["temperature", "temp"],
    "temperatury": ["temperature", "temp"],
    "temperature": ["temperature"],
    "wilgotnosc": ["humidity"],
    "wilgotnosci": ["humidity"],
    "swiatlo": ["light", "swiatlo"],
    "swiatla": ["light", "swiatlo"],
    "lampa": ["light", "lamp"],
    "lampka": ["light", "lampka"],
    "przelacznik": ["switch"],
    "gniazdko": ["switch", "gniazdk", "socket", "plug"],
    "gniazdka": ["switch", "gniazdk", "socket", "plug"],
    "ruch": ["motion", "occupancy", "md"],
    "ruchu": ["motion", "occupancy", "md"],
    "obecnosc": ["occupancy", "occu", "presence"],
    "obecnosci": ["occupancy", "occu", "presence"],
    "bateria": ["battery"],
    "baterii": ["battery"],
    "drzwi": ["door", "contact"],
    "okno": ["window"],
    "okna": ["window"],
    "klima": ["climate", "klima"],
    "klimatyzacja": ["climate", "klima"],
    "termostat": ["climate", "thermostat", "trv"],
    "grzejnik": ["climate", "trv"],
    "roleta": ["cover"],
    "zaluzje": ["cover"],
    "zamek": ["lock"],
    "kamera": ["camera"],
    "odkurzacz": ["vacuum"],
    "glosnik": ["media_player"],
    "zalanie": ["leak", "water"],
    "wyciek": ["leak", "water"],
    "dym": ["smoke"],
    "automatyzacja": ["automation"],
    "skrypt": ["script"],
    "scena": ["scene"],
}

#: Tokens too generic to carry signal — dropped before scoring.
STOPWORDS = frozenset({"w", "we", "na", "do", "z", "ze", "i", "the", "a", "of", "in"})

_PREFIX_LEN = 5


def normalize(text):
    """Lowercase, de-diacritic, collapse punctuation to single spaces."""
    if not text:
        return ""
    lowered = str(text).lower().translate(_PL)
    return re.sub(r"[^a-z0-9]+", " ", lowered).strip()


def tokenize(text):
    return [t for t in normalize(text).split() if t and t not in STOPWORDS]


def _term_score(term, entity_id_n, name_n, area_n, domain):
    """Score one term against one entity; returns (score, reason-or-None)."""
    score = 0
    where = []
    if term == domain:
        score += 3
        where.append(f"domain={domain}")
    if term in name_n:
        score += 3
        where.append("name")
    elif len(term) >= _PREFIX_LEN and term[:_PREFIX_LEN] in name_n:
        score += 2
        where.append("name~")
    if term in entity_id_n:
        score += 2
        where.append("entity_id")
    elif len(term) >= _PREFIX_LEN and term[:_PREFIX_LEN] in entity_id_n:
        score += 1
        where.append("entity_id~")
    if area_n and term in area_n:
        score += 3
        where.append("area")
    if not score:
        return 0, None
    return score, "+".join(where)


def score_entity(tokens, entity_id, friendly_name, area):
    """(total score, matched-token count, reason string) for one entity.

    Each query token scores at most once — its own form and its synonyms
    compete, best one wins — so a token with three synonyms cannot outweigh
    a token with none.
    """
    entity_id_n = normalize(entity_id)
    name_n = normalize(friendly_name)
    area_n = normalize(area)
    domain = entity_id.split(".", 1)[0] if "." in entity_id else ""

    total = 0
    matched = 0
    reasons = []
    for token in tokens:
        best = (0, None, None)
        for term in [token] + SYNONYMS.get(token, []):
            score, where = _term_score(term, entity_id_n, name_n, area_n, domain)
            if score > best[0]:
                best = (score, where, term)
        if best[0]:
            total += best[0]
            matched += 1
            term_note = f"'{token}'" if best[2] == token else f"'{token}'->'{best[2]}'"
            reasons.append(f"{term_note} in {best[1]}")
    return total, matched, "; ".join(reasons)
