"""Live PCA panel data for MCube AI — mirrors dashboard stats, analytics, agents, calls, objections."""

import logging
import re
from datetime import date, datetime, timedelta, timezone
from decimal import Decimal
from typing import Any, Callable, Dict, List, Optional, Tuple

logger = logging.getLogger(__name__)

_IST = timezone(timedelta(hours=5, minutes=30))
_db_handler = None


def _get_db():
    global _db_handler
    if _db_handler is None:
        from config import Config
        from db_handler import DatabaseHandler

        cfg = {key: getattr(Config, key) for key in dir(Config) if key.isupper()}
        _db_handler = DatabaseHandler(cfg)
    return _db_handler


def _ist_today() -> date:
    return datetime.now(_IST).date()


def _serialize(value: Any) -> Any:
    if isinstance(value, (datetime, date)):
        return value.isoformat()
    if isinstance(value, Decimal):
        return float(value)
    if isinstance(value, dict):
        return {k: _serialize(v) for k, v in value.items()}
    if isinstance(value, (list, tuple)):
        return [_serialize(v) for v in value]
    return value


def _compact_stats(stats: Optional[Dict[str, Any]]) -> Dict[str, Any]:
    if not stats:
        return {}
    return _serialize(
        {
            "total_calls": int(stats.get("total_calls") or 0),
            "answered_total": int(stats.get("answered_total") or 0),
            "inbound_total": int(stats.get("inbound_total") or 0),
            "outbound_total": int(stats.get("outbound_total") or 0),
            "inbound_answered": int(stats.get("inbound_answered") or 0),
            "outbound_answered": int(stats.get("outbound_answered") or 0),
            "inbound_not_answered": int(stats.get("inbound_not_answered") or 0),
            "outbound_not_answered": int(stats.get("outbound_not_answered") or 0),
            "avg_quality_score": round(float(stats.get("avg_quality_score") or 0), 2)
            if stats.get("avg_quality_score") is not None
            else None,
            "analyzed_total": int(stats.get("analyzed_total") or 0),
            "avg_answered_duration": int(stats.get("avg_answered_duration") or 0),
            "talk_listen_ratio": stats.get("talk_listen_ratio"),
        }
    )


def _extract_filters(metadata: Dict[str, Any]) -> Dict[str, Any]:
    meta = metadata or {}
    groupname = meta.get("groupname")
    date_from = meta.get("date_from")
    date_to = meta.get("date_to")
    snapshot = meta.get("snapshot")
    if isinstance(snapshot, dict):
        filters = snapshot.get("filters") or {}
        if not groupname:
            groupname = filters.get("groupname")
        date_from = date_from or filters.get("date_from") or filters.get("dateFrom")
        date_to = date_to or filters.get("date_to") or filters.get("dateTo")
    return {
        "groupname": groupname,
        "date_from": date_from,
        "date_to": date_to,
        "active_tab": meta.get("active_tab"),
        "route_path": meta.get("route_path"),
    }


def _detect_period_from_message(message: Optional[str]) -> Tuple[Optional[str], Optional[str], Optional[str]]:
    """Map user wording to a date range in IST."""
    text = str(message or "").lower()
    if not text.strip():
        return None, None, None

    today = _ist_today()
    if re.search(r"\btoday\b", text):
        d = today.isoformat()
        return d, d, "today"
    if re.search(r"\byesterday\b", text):
        d = (today - timedelta(days=1)).isoformat()
        return d, d, "yesterday"
    if re.search(r"\bthis week\b|\blast 7 days\b|\bpast week\b|\bthis week'?s\b", text):
        return (today - timedelta(days=6)).isoformat(), today.isoformat(), "last_7_days"
    if re.search(r"\bthis month\b|\bcurrent month\b", text):
        start = today.replace(day=1)
        return start.isoformat(), today.isoformat(), "this_month"
    return None, None, None


def _safe_run(label: str, fn: Callable[[], Any], facts: Dict[str, Any]) -> Any:
    try:
        return fn()
    except Exception as exc:
        logger.warning("panel_data %s failed: %s", label, exc)
        facts.setdefault("errors", {})[label] = str(exc)
        return None


def _period_bundle(
    db,
    bid: str,
    groupname: Optional[str],
    date_from: Optional[str],
    date_to: Optional[str],
    *,
    include_agents: bool = True,
    agent_limit: int = 10,
) -> Dict[str, Any]:
    stats = _compact_stats(db.get_location_stats(bid, groupname, date_from, date_to))
    bundle: Dict[str, Any] = {
        "date_from": date_from,
        "date_to": date_to,
        "stats": stats,
    }
    if include_agents:
        agents = db.get_agent_call_counts(
            bid, groupname, date_from, date_to, limit=agent_limit
        )
        bundle["agent_call_counts"] = agents or []
        if agents:
            bundle["top_agent_by_call_volume"] = agents[0]
    return bundle


def _summarize_sentiment(rows: Optional[List[Dict[str, Any]]]) -> Dict[str, Any]:
    totals: Dict[str, int] = {}
    if not rows:
        return totals
    for row in rows:
        sentiment = str(row.get("sentiment") or "unknown").lower()
        totals[sentiment] = totals.get(sentiment, 0) + int(row.get("count") or 0)
    return totals


def build_call_detail_panel_data(bid: str, call_id: str) -> Dict[str, Any]:
    db = _get_db()
    bid = str(bid)
    call_id = str(call_id)
    facts: Dict[str, Any] = {
        "scope": "call_detail",
        "call_id": call_id,
        "source": "database_live_query",
        "reference": {
            "timezone": "Asia/Kolkata",
            "today_ist": _ist_today().isoformat(),
        },
    }

    call = _serialize(db.get_call_by_id(bid, call_id) or {})
    analytics = _serialize(db.get_call_analytics(bid, call_id) or {})
    bant = _serialize(db.get_bant_analysis(bid, call_id) or {})
    transcript_obj = _serialize(db.get_call_transcript(bid, call_id) or {})

    transcript_text = str(transcript_obj.get("transcript") or "")
    if len(transcript_text) > 6000:
        transcript_obj["transcript"] = transcript_text[:6000] + "...(truncated)"
        transcript_obj["transcript_truncated"] = True

    facts["call"] = call
    facts["analytics"] = analytics
    facts["bant"] = bant
    facts["transcript"] = transcript_obj
    return facts


def build_dashboard_panel_data(
    bid: str,
    metadata: Optional[Dict[str, Any]] = None,
    message: Optional[str] = None,
) -> Dict[str, Any]:
    """Full dashboard panel context with explicit today / all-time / filtered periods."""
    filters = _extract_filters(metadata or {})
    db = _get_db()
    bid = str(bid)
    groupname = filters.get("groupname")
    dashboard_from = filters.get("date_from")
    dashboard_to = filters.get("date_to")
    today = _ist_today()
    today_str = today.isoformat()
    yesterday_str = (today - timedelta(days=1)).isoformat()
    week_from = (today - timedelta(days=6)).isoformat()

    facts: Dict[str, Any] = {
        "scope": "dashboard",
        "source": "database_live_query",
        "reference": {
            "timezone": "Asia/Kolkata",
            "today_ist": today_str,
            "now_ist": datetime.now(_IST).strftime("%Y-%m-%d %H:%M:%S"),
        },
        "filters": {k: v for k, v in filters.items() if v},
        "note": (
            "Use periods.today for 'today' questions, periods.all_time for lifetime totals, "
            "and question_focus when the user asks about a specific time range. "
            "Never invent call counts — use stats and agent_call_counts from this data."
        ),
    }

    periods: Dict[str, Any] = {}
    periods["today"] = _period_bundle(db, bid, groupname, today_str, today_str)
    periods["yesterday"] = _period_bundle(
        db, bid, groupname, yesterday_str, yesterday_str, include_agents=False
    )
    periods["last_7_days"] = _period_bundle(db, bid, groupname, week_from, today_str)
    periods["all_time"] = _period_bundle(db, bid, groupname, None, None)

    if dashboard_from or dashboard_to:
        periods["dashboard_filter"] = _period_bundle(
            db, bid, groupname, dashboard_from, dashboard_to
        )

    facts["periods"] = periods

    q_from, q_to, q_label = _detect_period_from_message(message)
    if q_label:
        facts["question_focus"] = {
            "label": q_label,
            **_period_bundle(db, bid, groupname, q_from, q_to, agent_limit=15),
        }

    # Primary stats: prefer dashboard filter if set, else all-time
    primary_from = dashboard_from
    primary_to = dashboard_to
    stats = _safe_run(
        "dashboard_stats",
        lambda: db.get_location_stats(bid, groupname, primary_from, primary_to),
        facts,
    )
    if stats:
        facts["dashboard_stats"] = _serialize(stats)

    overview = _safe_run(
        "analytics_overview",
        lambda: _serialize(db.get_analytics_overview(bid, groupname, primary_from, primary_to)),
        facts,
    )
    if overview:
        facts["analytics_overview"] = overview

    agent_counts = _safe_run(
        "agent_call_counts",
        lambda: db.get_agent_call_counts(bid, groupname, primary_from, primary_to, limit=25),
        facts,
    )
    if agent_counts:
        facts["agent_call_counts"] = agent_counts
        facts["top_agent_by_call_volume"] = agent_counts[0]

    leaderboard = _safe_run(
        "agent_quality_leaderboard",
        lambda: db.get_agent_leaderboard(bid, groupname, primary_from, primary_to),
        facts,
    )
    if leaderboard:
        facts["agent_quality_leaderboard"] = leaderboard[:15]
        facts["top_agent_by_quality_score"] = leaderboard[0]

    quality_by_agent = _safe_run(
        "quality_by_agent",
        lambda: _serialize(db.get_quality_by_agent(bid, groupname, primary_from, primary_to)),
        facts,
    )
    if quality_by_agent:
        facts["quality_by_agent"] = quality_by_agent

    call_purposes = _safe_run(
        "call_purposes",
        lambda: _serialize(db.get_call_purpose_frequency(bid, groupname, primary_from, primary_to)),
        facts,
    )
    if call_purposes:
        facts["call_purposes"] = (call_purposes or [])[:15]

    concerns = _safe_run(
        "objections_and_concerns",
        lambda: _serialize(db.get_concerns_frequency(bid, groupname, primary_from, primary_to)),
        facts,
    )
    if concerns:
        facts["objections_and_concerns"] = (concerns or [])[:15]

    sentiment_rows = _safe_run(
        "sentiment_by_location",
        lambda: db.get_sentiment_by_location(bid, groupname, primary_from, primary_to),
        facts,
    )
    if sentiment_rows:
        facts["sentiment_by_location"] = _serialize(sentiment_rows[:30])
        facts["sentiment_totals"] = _summarize_sentiment(sentiment_rows)

    quality_by_location = _safe_run(
        "quality_by_location",
        lambda: _serialize(db.get_quality_by_location(bid, groupname, primary_from, primary_to)),
        facts,
    )
    if quality_by_location:
        facts["quality_by_location"] = (quality_by_location or [])[:15]

    busy_locations = _safe_run(
        "busy_locations",
        lambda: _serialize(db.get_busy_locations(bid, groupname, primary_from, primary_to)),
        facts,
    )
    if busy_locations:
        facts["busy_locations"] = (busy_locations or [])[:15]

    groups = _safe_run("groups", lambda: db.get_all_groupnames(bid), facts)
    if groups:
        facts["groups"] = (groups or [])[:20]

    recent = _safe_run(
        "recent_calls",
        lambda: _serialize(
            db.get_filtered_raw_calls(
                bid,
                groupname,
                None,
                None,
                limit=15,
                offset=0,
                date_from=primary_from,
                date_to=primary_to,
            )
        ),
        facts,
    )
    if recent:
        calls = recent if isinstance(recent, list) else recent.get("calls") or []
        facts["recent_calls"] = calls[:15]
        if calls:
            facts["latest_call"] = calls[0]

    today_calls = _safe_run(
        "today_calls",
        lambda: _serialize(
            db.get_filtered_raw_calls(
                bid,
                groupname,
                None,
                None,
                limit=20,
                offset=0,
                date_from=today_str,
                date_to=today_str,
            )
        ),
        facts,
    )
    if today_calls:
        calls = today_calls if isinstance(today_calls, list) else today_calls.get("calls") or []
        facts["today_calls"] = calls[:20]

    return facts


def build_panel_data(
    bid: str,
    metadata: Optional[Dict[str, Any]] = None,
    message: Optional[str] = None,
) -> Dict[str, Any]:
    meta = metadata or {}
    if meta.get("scope") == "call_detail" and meta.get("call_id"):
        return build_call_detail_panel_data(bid, str(meta["call_id"]))
    return build_dashboard_panel_data(bid, meta, message=message)


def attach_live_analytics(
    bid: str,
    metadata: Optional[Dict[str, Any]] = None,
    message: Optional[str] = None,
) -> Dict[str, Any]:
    meta = dict(metadata or {})
    try:
        panel = build_panel_data(bid, meta, message=message)
        meta["panel_data"] = panel
        meta["live_analytics"] = panel
    except Exception as exc:
        logger.warning("attach_live_analytics failed bid=%s: %s", bid, exc)
    return meta
