from __future__ import annotations

from rights_filter.domain.records import Evidence, EvidenceSource, ScoreResult


class RiskScorer:
    def score(self, evidence: list[Evidence]) -> ScoreResult:
        score = 0
        reasons: list[str] = []
        counted_web_buckets: set[str] = set()

        for item in evidence:
            if item.data.get("operator_status") in {"irrelevant", "false_positive"}:
                continue
            if item.source == EvidenceSource.FINGERPRINT:
                if item.data.get("contributed") is False or item.data.get("status") == "queued":
                    continue
                similarity = _safe_float(item.data.get("similarity", 0))
                if similarity >= 0.9:
                    score += 80
                    reasons.append(item.reason)
                elif item.reason != "Image fingerprints generated":
                    score += 30
                    reasons.append(item.reason)
            elif item.source == EvidenceSource.FACE_PERSON:
                score += 35
                reasons.append(item.reason)
            elif item.source == EvidenceSource.WEB_DETECTION:
                bucket = _web_detection_bucket(item)
                if bucket and bucket in counted_web_buckets:
                    continue
                points = _web_detection_points(item)
                score += points
                if points:
                    reasons.append(item.reason)
                    if bucket:
                        counted_web_buckets.add(bucket)
            elif item.source == EvidenceSource.NAVER_SEARCH:
                points = _naver_search_points(item)
                score += points
                if points:
                    reasons.append(item.reason)
            elif item.source == EvidenceSource.LLM_SUMMARY:
                continue
            elif item.source in {
                EvidenceSource.FAILURE,
                EvidenceSource.EXTERNAL_SKIPPED,
                EvidenceSource.SEARCH_SKIPPED,
                EvidenceSource.ENRICHMENT_FAILURE,
            }:
                # An LLM-summary failure only means narration didn't run; it is
                # not a search/coverage gap, so it must not add risk or surface
                # as a reason. Genuine provider failures (search / web-detection,
                # e.g. "External API failed") still contribute the +30 signal.
                if item.reason.startswith("LLM assistance failed"):
                    continue
                score += (
                    30
                    if item.source
                    in {EvidenceSource.FAILURE, EvidenceSource.ENRICHMENT_FAILURE}
                    else 0
                )
                reasons.append(item.reason)

        score = min(100, score)
        if score >= 70:
            band = "high"
        elif score >= 30:
            band = "medium"
        else:
            band = "low"

        return ScoreResult(score=score, band=band, reasons=_unique(reasons))


def _web_detection_points(item: Evidence) -> int:
    if item.data.get("weak_hint"):
        return 0
    if item.data.get("match") == "full":
        return 45
    if item.data.get("match") in {"partial", "page"}:
        return 35
    if item.data.get("match") == "visual":
        return 10
    if item.data.get("url"):
        return 45
    if item.data.get("category") in {"character", "celebrity", "work"}:
        return 45
    if item.data.get("entity"):
        return round(40 * item.confidence)
    return 0


def _web_detection_bucket(item: Evidence) -> str:
    match = str(item.data.get("match", ""))
    if match in {"full", "partial", "page", "visual", "entity"}:
        return match
    if item.data.get("entity"):
        return "entity"
    if item.data.get("url"):
        return "url"
    return ""


def _naver_search_points(item: Evidence) -> int:
    if item.data.get("promoted"):
        return round(50 * item.confidence)
    return 0


def _unique(values: list[str]) -> list[str]:
    result: list[str] = []
    for value in values:
        if value and value not in result:
            result.append(value)
    return result


def _safe_float(value: object) -> float:
    try:
        return float(value or 0)
    except (TypeError, ValueError):
        return 0.0
