"""Score customer sentiment from transcript using keywords, LLM, or hybrid rules."""

from __future__ import annotations

import re
from typing import Any, Dict, List, Optional, Tuple

from sentiment_config_handler import SentimentConfigHandler


def _split_keywords(text: str) -> List[str]:
    if not text:
        return []
    parts = re.split(r"[\n,;|]+", str(text))
    return [p.strip().lower() for p in parts if p.strip()]


def _count_keyword_hits(haystack: str, keywords: List[str], max_hits: int) -> Tuple[int, List[str]]:
    hits: List[str] = []
    if not haystack or not keywords:
        return 0, hits
    text = haystack.lower()
    for kw in keywords:
        if len(hits) >= max_hits:
            break
        if kw and kw in text:
            hits.append(kw)
    return len(hits), hits


def _normalize_sentiment_label(value: Any) -> str:
    text = str(value or "").strip().lower()
    if "positive" in text or text in {"pos", "good", "happy"}:
        return "positive"
    if "negative" in text or text in {"neg", "bad", "angry"}:
        return "negative"
    return "neutral"


def extract_customer_text(
    transcript: str,
    speaker_segments: Optional[List[Dict]] = None,
) -> str:
    """Return customer-only utterances when diarized segments are available."""
    if not speaker_segments:
        return str(transcript or "")

    customer_lines: List[str] = []
    for segment in speaker_segments:
        role = str(segment.get("role") or "").lower()
        speaker = str(segment.get("speaker") or segment.get("speaker_label") or "").lower()
        text = str(segment.get("text") or segment.get("transcript") or "").strip()
        if not text:
            continue
        is_customer = role == "customer" or (
            not role and ("customer" in speaker or speaker.endswith("_1") or speaker == "speaker_1")
        )
        if is_customer:
            customer_lines.append(text)

    if customer_lines:
        return "\n".join(customer_lines)
    return str(transcript or "")


def keyword_sentiment_label(
    config: Dict[str, Any],
    analysis_text: str,
) -> Tuple[str, Dict[str, Any]]:
    max_hits = max(1, int(config.get("max_keyword_hits") or 5))
    pos_kw = _split_keywords(config.get("positive_keywords") or "")
    neg_kw = _split_keywords(config.get("negative_keywords") or "")
    neu_kw = _split_keywords(config.get("neutral_keywords") or "")

    pos_count, pos_hits = _count_keyword_hits(analysis_text, pos_kw, max_hits)
    neg_count, neg_hits = _count_keyword_hits(analysis_text, neg_kw, max_hits)
    neu_count, neu_hits = _count_keyword_hits(analysis_text, neu_kw, max_hits)

    pos_threshold = max(1, int(config.get("keyword_positive_threshold") or 2))
    neg_threshold = max(1, int(config.get("keyword_negative_threshold") or 2))

    if neg_count >= neg_threshold and neg_count > pos_count:
        label = "negative"
    elif pos_count >= pos_threshold and pos_count > neg_count:
        label = "positive"
    elif neu_count > 0 and pos_count == 0 and neg_count == 0:
        label = "neutral"
    elif pos_count > neg_count:
        label = "positive"
    elif neg_count > pos_count:
        label = "negative"
    else:
        label = "neutral"

    details = {
        "positive_hits": pos_hits,
        "negative_hits": neg_hits,
        "neutral_hits": neu_hits,
        "positive_count": pos_count,
        "negative_count": neg_count,
        "neutral_count": neu_count,
        "method": "keyword",
    }
    return label, details


def build_sentiment_prompt_block(
    config: Dict[str, Any],
    analysis_text: str,
    keyword_details: Optional[Dict[str, Any]] = None,
) -> str:
    rubric = (config.get("prompt_rubric") or "").strip()
    mode = str(config.get("detection_mode") or "hybrid").lower()
    keyword_details = keyword_details or {}

    lines = [
        "CUSTOMER SENTIMENT:",
        "Evaluate mood from the customer's words only (ignore agent tone).",
    ]
    if rubric:
        lines.append(f"Rubric: {rubric}")
    if config.get("customer_only"):
        lines.append("Use the CUSTOMER UTTERANCES section below as the primary evidence.")
    if mode == "hybrid" and keyword_details:
        pos = keyword_details.get("positive_hits") or []
        neg = keyword_details.get("negative_hits") or []
        if pos or neg:
            lines.append(
                f"Keyword hints — positive: {', '.join(pos) or 'none'}; "
                f"negative: {', '.join(neg) or 'none'}."
            )
    lines.append('Return sentiment as exactly one of: "positive", "negative", or "neutral".')
    if analysis_text.strip():
        lines.append(f"\nCUSTOMER UTTERANCES:\n{analysis_text.strip()}")
    return "\n".join(lines)


def finalize_sentiment(
    config: Dict[str, Any],
    llm_sentiment: Optional[str],
    keyword_label: str,
    keyword_details: Dict[str, Any],
) -> Tuple[str, Dict[str, Any]]:
    mode = str(config.get("detection_mode") or "hybrid").lower()
    llm_label = _normalize_sentiment_label(llm_sentiment) if llm_sentiment else None

    if mode == "keyword":
        return keyword_label, {**keyword_details, "method": "keyword", "llm_sentiment": llm_label}

    if mode == "llm":
        final = llm_label or keyword_label or "neutral"
        return final, {
            **keyword_details,
            "method": "llm",
            "llm_sentiment": llm_label,
            "keyword_sentiment": keyword_label,
        }

    # hybrid: strong keyword signal overrides ambiguous LLM; agree when possible
    pos_count = int(keyword_details.get("positive_count") or 0)
    neg_count = int(keyword_details.get("negative_count") or 0)
    pos_threshold = max(1, int(config.get("keyword_positive_threshold") or 2))
    neg_threshold = max(1, int(config.get("keyword_negative_threshold") or 2))

    if neg_count >= neg_threshold and neg_count > pos_count:
        final = "negative"
    elif pos_count >= pos_threshold and pos_count > neg_count:
        final = "positive"
    elif llm_label:
        final = llm_label
    else:
        final = keyword_label or "neutral"

    return final, {
        **keyword_details,
        "method": "hybrid",
        "llm_sentiment": llm_label,
        "keyword_sentiment": keyword_label,
    }


def prepare_sentiment_analysis(
    bid: str,
    transcript: str,
    speaker_segments: Optional[List[Dict]] = None,
    config: Optional[Any] = None,
    handler: Optional[SentimentConfigHandler] = None,
) -> Dict[str, Any]:
    """Build analysis text, keyword scan, and optional prompt block for Nova."""
    handler = handler or SentimentConfigHandler(config or {})
    handler.ensure_table()
    cfg = handler.get_config_or_defaults(bid)

    use_customer_only = bool(cfg.get("customer_only", True))
    analysis_text = (
        extract_customer_text(transcript, speaker_segments)
        if use_customer_only
        else str(transcript or "")
    )
    keyword_label, keyword_details = keyword_sentiment_label(cfg, analysis_text)
    prompt_block = build_sentiment_prompt_block(cfg, analysis_text, keyword_details)

    return {
        "config": cfg,
        "analysis_text": analysis_text,
        "keyword_label": keyword_label,
        "keyword_details": keyword_details,
        "prompt_block": prompt_block,
        "include_in_llm_prompt": str(cfg.get("detection_mode") or "hybrid").lower() != "keyword",
    }


def resolve_call_sentiment(
    bid: str,
    transcript: str,
    speaker_segments: Optional[List[Dict]] = None,
    llm_sentiment: Optional[str] = None,
    config: Optional[Any] = None,
    handler: Optional[SentimentConfigHandler] = None,
    prepared: Optional[Dict[str, Any]] = None,
) -> Tuple[str, Dict[str, Any]]:
    """Return final sentiment label and detection metadata."""
    if prepared is None:
        prepared = prepare_sentiment_analysis(
            bid,
            transcript,
            speaker_segments,
            config=config,
            handler=handler,
        )
    cfg = prepared["config"]
    label, details = finalize_sentiment(
        cfg,
        llm_sentiment,
        prepared["keyword_label"],
        prepared["keyword_details"],
    )
    details["analysis_text_length"] = len(prepared.get("analysis_text") or "")
    details["customer_only"] = bool(cfg.get("customer_only", True))
    details["detection_mode"] = str(cfg.get("detection_mode") or "hybrid")
    return label, details
