"""AI matchmaking for SP3 linkage (FRSME-MPL005, FRSME-MPL006).

Builds a `RecommendationSnapshot` per entrepreneur scoring partner /
service-provider directory entries against the entrepreneur's verified
businesses (sectors, stages, geographic reach). The actual scoring is
delegated to ``apps.core.ai.client.recommend`` which falls back to a
deterministic ranker when no AI backend is reachable.
"""
from __future__ import annotations

import hashlib
import json
import logging
from typing import Iterable

from django.utils import timezone

from apps.core.ai.client import recommend
from apps.smehub.linkage.models import (
    PartnerDirectoryEntry,
    RecommendationSnapshot,
    ServiceProviderEntry,
)
from apps.smehub.marketplace.models import BuyerRegistryEntry
from apps.smehub.onboarding.models import Business, EntrepreneurProfile

logger = logging.getLogger(__name__)


def _profile_features(entrepreneur: EntrepreneurProfile) -> dict:
    businesses = list(
        Business.objects.filter(
            entrepreneur=entrepreneur,
            verification_status=Business.Status.VERIFIED,
        ).values("sector", "business_stage", "country"),
    )
    sectors = sorted({b["sector"] for b in businesses if b["sector"]})
    stages = sorted({b["business_stage"] for b in businesses if b["business_stage"]})
    geo = sorted({b["country"] for b in businesses if b["country"]})
    return {
        "sectors": sectors,
        "stages": stages,
        "geo": geo,
        "stage": stages[0] if stages else "",
    }


def _candidate_payload(entries: Iterable, kind: str, *, sectors_attr: str = "sector_focus") -> list[dict]:
    payload: list[dict] = []
    for entry in entries:
        payload.append(
            {
                "id": entry.pk,
                "label": entry.organisation_name,
                "kind": kind,
                "sectors": list(getattr(entry, sectors_attr, None) or []),
                "geo": list(entry.geographic_coverage or []),
                "stages": [],
                "org_type": entry.org_type,
            }
        )
    return payload


def _all_candidates() -> list[dict]:
    """FRSME-MPL005 — candidate pool spans all three directories: partners,
    service providers, and buyers (verified only)."""
    partners = PartnerDirectoryEntry.objects.filter(
        verification_status=PartnerDirectoryEntry.Status.VERIFIED,
    )
    providers = ServiceProviderEntry.objects.filter(
        verification_status=ServiceProviderEntry.Status.VERIFIED,
    )
    buyers = BuyerRegistryEntry.objects.filter(
        verification_status=BuyerRegistryEntry.Status.VERIFIED,
    )
    return (
        _candidate_payload(partners, "partner")
        + _candidate_payload(providers, "service_provider")
        + _candidate_payload(buyers, "buyer", sectors_attr="sectors_sourced")
    )


def _hash_inputs(profile: dict, candidate_count: int) -> str:
    payload = json.dumps({"p": profile, "c": candidate_count}, sort_keys=True)
    return hashlib.sha256(payload.encode("utf-8")).hexdigest()


def recommend_partners(entrepreneur: EntrepreneurProfile, *, top_k: int = 8) -> list[dict]:
    """Return ranked recommendations without persisting a snapshot."""
    profile = _profile_features(entrepreneur)
    candidates = _all_candidates()
    return recommend(profile, candidates, top_k=top_k)


def refresh_recommendations(
    entrepreneur: EntrepreneurProfile,
    *,
    trigger: str = "manual",
    top_k: int = 8,
) -> RecommendationSnapshot:
    """Compute + persist a snapshot. Idempotent — overwrites the per-user row."""
    profile = _profile_features(entrepreneur)
    candidates = _all_candidates()
    ranked = recommend(profile, candidates, top_k=top_k)
    is_fallback = bool(ranked and ranked[0].get("is_fallback", True))
    snapshot, _ = RecommendationSnapshot.objects.update_or_create(
        entrepreneur=entrepreneur,
        defaults={
            "recommendations": ranked,
            "based_on": {
                "profile": profile,
                "candidate_count": len(candidates),
                "fingerprint": _hash_inputs(profile, len(candidates)),
            },
            "refreshed_at": timezone.now(),
            "refreshed_by_signal": trigger,
            "is_fallback": is_fallback,
        },
    )
    return snapshot
