"""
Confidence Scoring Module - Composite 0-100 risk/confidence score.

Combines multiple signals into a single interpretable score with
human-readable breakdown. Goes beyond "this might be a scam" to
"92% confidence: 17 signals, 3 corroborating sources, deployer
fingerprint matches 2 known scammers."

Signals weighted:
  - Retrieval concentration: how concentrated are top results? (25%)
  - Similarity quality: raw similarity scores (20%)
  - Source corroboration: independent sources agreeing (20%)
  - Reranker margin: gap between top and runner-up (15%)
  - Temporal freshness: how recent is the evidence? (10%)
  - Entity exact-match bonus: direct address/token match (10%)
"""

import logging
from datetime import UTC
from typing import Any

logger = logging.getLogger(__name__)

# ── Weights (sum to 1.0) ──────────────────────────────────────────
WEIGHT_RETRIEVAL_CONCENTRATION = 0.25
WEIGHT_SIMILARITY_QUALITY = 0.20
WEIGHT_SOURCE_CORROBORATION = 0.20
WEIGHT_RERANKER_MARGIN = 0.15
WEIGHT_TEMPORAL_FRESHNESS = 0.10
WEIGHT_ENTITY_BONUS = 0.10


def score_confidence(
    results: list[dict[str, Any]],
    query: str = "",
    entity_matches: list[str] | None = None,
) -> dict[str, Any]:
    """
    Compute composite confidence score from RAG search results.

    Parameters
    ----------
    results: List of result dicts from three_pillar_search, each containing:
        - similarity (float): vector similarity 0-1
        - combined_score (float, optional): post-rerank combined score
        - source (str, optional): source identifier
        - metadata (dict, optional): may contain 'created_at', 'severity'
    query: The original query string (for context)
    entity_matches: List of entity strings that had exact matches

    Returns
    -------
    Dict with:
        - score: int 0-100
        - label: "CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "INCONCLUSIVE"
        - breakdown: Dict of component scores
        - summary: Human-readable one-liner
        - details: List of specific signal descriptions
    """
    if not results:
        return {
            "score": 0,
            "label": "INCONCLUSIVE",
            "breakdown": {},
            "summary": "No evidence found - insufficient data for confidence assessment.",
            "details": [],
        }

    components = {}
    details = []

    # 1. Retrieval Concentration
    concentration = _score_concentration(results)
    components["retrieval_concentration"] = round(concentration, 2)
    if concentration > 0.8:
        details.append("Strong signal: top results highly concentrated")
    elif concentration < 0.3:
        details.append("Weak signal: results are scattered - low confidence pattern")

    # 2. Similarity Quality
    sim_quality = _score_similarity(results)
    components["similarity_quality"] = round(sim_quality, 2)
    if sim_quality > 0.8:
        details.append("High semantic similarity to known patterns")
    elif sim_quality < 0.5:
        details.append("Low similarity - no strong matches found")

    # 3. Source Corroboration
    corroboration = _score_corroboration(results)
    components["source_corroboration"] = round(corroboration, 2)
    unique_sources = len({r.get("source", "") for r in results if r.get("source")})
    if unique_sources >= 3:
        details.append(f"Corroborated by {unique_sources} independent sources")
    elif unique_sources == 0:
        details.append("No source metadata available - cannot verify independence")

    # 4. Reranker Margin
    margin = _score_reranker_margin(results)
    components["reranker_margin"] = round(margin, 2)
    if margin > 0.8:
        details.append("Clear leader: top result significantly stronger than alternatives")
    elif margin < 0.3 and len(results) > 1:
        details.append("Close race - multiple results nearly equal")

    # 5. Temporal Freshness
    freshness = _score_freshness(results)
    components["temporal_freshness"] = round(freshness, 2)
    if freshness > 0.8:
        details.append("Evidence is recent (< 7 days)")
    elif freshness < 0.3:
        details.append("Evidence is old (> 90 days) - may be stale")

    # 6. Entity Exact-Match Bonus
    entity_bonus = _score_entity_match(entity_matches, query)
    components["entity_bonus"] = round(entity_bonus, 2)
    if entity_bonus > 0.5:
        details.append(f"Exact address/token match found: {', '.join(entity_matches[:3])}")

    # ── Composite Score ──────────────────────────────────────────
    composite = (
        WEIGHT_RETRIEVAL_CONCENTRATION * concentration
        + WEIGHT_SIMILARITY_QUALITY * sim_quality
        + WEIGHT_SOURCE_CORROBORATION * corroboration
        + WEIGHT_RERANKER_MARGIN * margin
        + WEIGHT_TEMPORAL_FRESHNESS * freshness
        + WEIGHT_ENTITY_BONUS * entity_bonus
    )

    score = min(100, max(0, round(composite * 100)))

    # ── Label ─────────────────────────────────────────────────────
    if score >= 90:
        label = "CRITICAL"
        summary = f"Extremely high confidence ({score}%) - multiple strong corroborating signals."
    elif score >= 75:
        label = "HIGH"
        summary = f"High confidence ({score}%) - strong evidence from multiple signals."
    elif score >= 55:
        label = "MEDIUM"
        summary = f"Moderate confidence ({score}%) - some signals present but not definitive."
    elif score >= 30:
        label = "LOW"
        summary = f"Low confidence ({score}%) - weak or conflicting signals."
    else:
        label = "INCONCLUSIVE"
        summary = f"Insufficient evidence ({score}%) - need more data for assessment."

    # Add signal count to summary
    signal_count = sum(1 for v in components.values() if v > 0.3)
    if signal_count > 0:
        summary += f" Based on {signal_count} signal categories."

    return {
        "score": score,
        "label": label,
        "breakdown": components,
        "summary": summary,
        "details": details,
    }


# ── Component Scorers ──────────────────────────────────────────────


def _score_concentration(results: list[dict]) -> float:
    """How concentrated are the top similarities? Sharp drop = strong signal."""
    sims = [r.get("similarity", 0) or r.get("combined_score", 0) for r in results[:5]]
    sims = [s for s in sims if s is not None and s > 0]
    if not sims:
        return 0.0
    if len(sims) == 1:
        return 0.5  # single result, neutral

    top = sims[0]
    avg_rest = sum(sims[1:]) / len(sims[1:]) if len(sims) > 1 else 0
    if top == 0:
        return 0.0

    # Ratio of top to average-of-rest. High ratio = high concentration.
    ratio = min(1.0, (top - avg_rest) / top) if top > avg_rest else 0.0
    return ratio


def _score_similarity(results: list[dict]) -> float:
    """Raw semantic similarity quality - how close are top results?"""
    sims = [r.get("similarity", 0) or r.get("combined_score", 0) for r in results[:3]]
    sims = [s for s in sims if s is not None and s > 0]
    if not sims:
        return 0.0

    # Average of top-3 similarities, capped at 0.95 (cosine can be near 1.0 for duplicates)
    avg = sum(sims) / len(sims)
    return min(1.0, avg / 0.9)  # normalize: 0.9+ similarity = 1.0 score


def _score_corroboration(results: list[dict]) -> float:
    """How many independent sources agree?"""
    sources = [r.get("source", "") for r in results if r.get("source")]
    unique = len(set(sources))
    if unique >= 5:
        return 1.0
    elif unique >= 3:
        return 0.8
    elif unique >= 2:
        return 0.5
    elif unique == 1:
        return 0.2
    return 0.0


def _score_reranker_margin(results: list[dict]) -> float:
    """Gap between #1 and #2 combined score. Big gap = clear winner."""
    scores = [r.get("combined_score", r.get("similarity", 0)) for r in results[:3]]
    scores = [s for s in scores if s is not None and s > 0]
    if len(scores) < 2:
        return 0.5  # single result, neutral
    margin = scores[0] - scores[1]
    # Normalize: margin of 0.2+ = 1.0 score
    return min(1.0, margin / 0.2)


def _score_freshness(results: list[dict]) -> float:
    """How recent is the evidence?"""
    from datetime import datetime

    now = datetime.now(UTC)
    ages_days = []

    for r in results:
        created = r.get("metadata", {}).get("created_at") or r.get("created_at")
        if created:
            try:
                dt = datetime.fromisoformat(created.replace("Z", "+00:00")) if isinstance(created, str) else created
                age = (now - dt).days if hasattr(dt, "days") else 365
                ages_days.append(age)
            except (ValueError, TypeError):
                pass

    if not ages_days:
        return 0.5  # no freshness data, neutral

    avg_age = sum(ages_days) / len(ages_days)
    # Score decays: < 1 day = 1.0, 7 days = 0.8, 30 days = 0.5, 90 days = 0.3, > 365 = 0.1
    if avg_age <= 1:
        return 1.0
    elif avg_age <= 7:
        return 0.9
    elif avg_age <= 30:
        return 0.7
    elif avg_age <= 90:
        return 0.4
    elif avg_age <= 180:
        return 0.2
    return 0.1


def _score_entity_match(entity_matches: list[str] | None, query: str) -> float:
    """Bonus for exact entity matches (address, token symbol, hash)."""
    if not entity_matches:
        # Check query for address patterns as a fallback
        import re

        eth_match = re.search(r"0x[a-fA-F0-9]{40}", query)
        sol_match = re.search(r"[1-9A-HJ-NP-Za-km-z]{32,44}", query)
        if eth_match or sol_match:
            return 0.5  # query contains an address - strong signal
        return 0.0

    # Score based on match count
    count = len(entity_matches)
    if count >= 3:
        return 1.0
    elif count >= 2:
        return 0.8
    return 0.6
