from __future__ import annotations

import re
from typing import Any


def normalize_token(value: str) -> str:
    value = re.sub(r"[^a-z0-9]+", " ", value.lower())
    return " ".join(part for part in value.split() if part)


def extract_ingredients(payload: dict[str, Any]) -> list[str]:
    out: list[str] = []
    seen: set[str] = set()
    for item in payload.get("items") or []:
        name = normalize_token(str(item.get("name") or ""))
        if not name or name in seen:
            continue
        seen.add(name)
        out.append(name)
    return out


def extract_tags(payload: dict[str, Any]) -> list[str]:
    return [normalize_token(str(tag)) for tag in (payload.get("tags") or []) if normalize_token(str(tag))]


def score_recipe(query: str, payload: dict[str, Any], profile: dict[str, Any]) -> tuple[int, list[str]]:
    score = 50
    reasons: list[str] = []
    haystack = " ".join(
        [
            normalize_token(str(payload.get("name") or "")),
            normalize_token(str(payload.get("description") or "")),
            " ".join(extract_ingredients(payload)),
            " ".join(extract_tags(payload)),
        ]
    )
    query_terms = [term for term in normalize_token(query).split() if len(term) > 2]
    if query_terms:
        matched = sum(1 for term in query_terms if term in haystack)
        score += matched * 6
        if matched:
            reasons.append(f"Matches query terms: {matched}")
    ingredients = extract_ingredients(payload)
    tags = extract_tags(payload)
    approved_ingredients = {normalize_token(k): int(v) for k, v in (profile.get("approved_ingredients") or {}).items()}
    rejected_ingredients = {normalize_token(k): int(v) for k, v in (profile.get("rejected_ingredients") or {}).items()}
    approved_tags = {normalize_token(k): int(v) for k, v in (profile.get("approved_tags") or {}).items()}
    rejected_tags = {normalize_token(k): int(v) for k, v in (profile.get("rejected_tags") or {}).items()}
    soft_blacklist = {normalize_token(item.get("name", "")) for item in (profile.get("soft_blacklist") or []) if item.get("enabled")}
    for term in sorted(soft_blacklist):
        if term and term in haystack:
            score -= 30
            reasons.append(f"Soft blacklist term: {term}")
    liked_hits = 0
    for ingredient in ingredients:
        liked_hits += approved_ingredients.get(ingredient, 0)
        score -= min(rejected_ingredients.get(ingredient, 0) * 7, 18)
    for tag in tags:
        liked_hits += approved_tags.get(tag, 0)
        score -= min(rejected_tags.get(tag, 0) * 5, 12)
    if liked_hits:
        score += min(liked_hits * 3, 18)
        reasons.append("Aligned with prior approvals")
    title = str(payload.get("name") or "")
    if len(title.split()) <= 10:
        score += 3
        reasons.append("Clear short title")
    total_time = payload.get("time")
    if isinstance(total_time, int):
        if total_time <= 45:
            score += 6
            reasons.append("Weeknight-friendly time")
        elif total_time >= 120:
            score -= 6
    description = str(payload.get("description") or "")
    if description.count("\n") >= 3:
        score += 4
        reasons.append("Structured instructions")
    return max(0, min(100, score)), reasons
