import json
import os


def _split_csv(value):
    if not value:
        return set()
    return {item.strip() for item in value.split(",") if item.strip()}


def _score_charity(preferences, charity):
    preferred_causes = _split_csv(preferences.get("causes"))
    preferred_support = _split_csv(preferences.get("support_types"))
    charity_causes = _split_csv(charity.get("cause_tags"))
    charity_support = _split_csv(charity.get("support_types"))

    cause_matches = preferred_causes.intersection(charity_causes)
    support_matches = preferred_support.intersection(charity_support)

    score = len(cause_matches) * 35 + len(support_matches) * 25

    preferred_location = (preferences.get("location") or "").strip().lower()
    charity_location = (charity.get("service_area") or "").strip().lower()
    if preferred_location and (
        preferred_location in charity_location or charity_location in preferred_location
    ):
        score += 15

    if preferences.get("motivation"):
        motivation_words = set(preferences["motivation"].lower().split())
        description_words = set((charity.get("description") or "").lower().split())
        score += min(len(motivation_words.intersection(description_words)) * 3, 15)

    return score, cause_matches, support_matches


def _fallback_explanation(charity, cause_matches, support_matches):
    cause_text = ", ".join(sorted(cause_matches)) or "your selected causes"
    support_text = ", ".join(sorted(support_matches)) or "your preferred way to help"
    return (
        f"{charity['name']} is a strong fit because it aligns with {cause_text} "
        f"and accepts support through {support_text}."
    )


def _llm_explanations(preferences, ranked):
    api_key = os.getenv("OPENAI_API_KEY")
    if not api_key or not ranked:
        return {}

    try:
        from openai import OpenAI
    except ImportError:
        return {}

    client = OpenAI(api_key=api_key)
    model = os.getenv("OPENAI_MODEL", "gpt-4o-mini")

    payload = {
        "preferences": {
            "causes": preferences.get("causes"),
            "support_types": preferences.get("support_types"),
            "location": preferences.get("location"),
            "budget": preferences.get("budget"),
            "availability": preferences.get("availability"),
            "motivation": preferences.get("motivation"),
        },
        "charities": [
            {
                "id": item["charity"]["id"],
                "name": item["charity"]["name"],
                "description": item["charity"]["description"],
                "website_url": item["charity"]["website_url"],
                "cause_tags": item["charity"]["cause_tags"],
                "support_types": item["charity"]["support_types"],
                "score": item["score"],
            }
            for item in ranked
        ],
    }

    response = client.chat.completions.create(
        model=model,
        messages=[
            {
                "role": "system",
                "content": (
                    "You write concise, practical charity recommendation explanations. "
                    "Return JSON only as an object keyed by charity id. Each value should "
                    "be one sentence under 35 words."
                ),
            },
            {"role": "user", "content": json.dumps(payload)},
        ],
        response_format={"type": "json_object"},
    )

    try:
        return json.loads(response.choices[0].message.content)
    except (TypeError, json.JSONDecodeError, AttributeError):
        return {}


def build_recommendations(preferences, charities, limit=5):
    ranked = []
    for charity in charities:
        score, cause_matches, support_matches = _score_charity(preferences, charity)
        if score > 0:
            ranked.append(
                {
                    "charity": charity,
                    "score": score,
                    "cause_matches": cause_matches,
                    "support_matches": support_matches,
                    "explanation": _fallback_explanation(
                        charity, cause_matches, support_matches
                    ),
                }
            )

    ranked.sort(key=lambda item: item["score"], reverse=True)
    ranked = ranked[:limit]

    llm_text = _llm_explanations(preferences, ranked)
    for item in ranked:
        charity_id = str(item["charity"]["id"])
        if charity_id in llm_text:
            item["explanation"] = llm_text[charity_id]

    return ranked
