from __future__ import annotations

from collections import Counter
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List, Optional

from sqlalchemy import Integer, cast, func, select
from sqlalchemy.ext.asyncio import AsyncSession

from ..models import SaaSEvent, SaaSEventStatus

SUCCESS_STATUSES = {SaaSEventStatus.SUCCESS, SaaSEventStatus.COMPLETED}


def _ensure_utc(value: datetime) -> datetime:
    if value.tzinfo is None:
        return value.replace(tzinfo=timezone.utc)
    return value.astimezone(timezone.utc)


def _json_attr_expr(db: AsyncSession, key: str):
    dialect = getattr(db.bind, "dialect", None)
    if dialect and dialect.name == "sqlite":
        return func.json_extract(SaaSEvent.attrs, f"$.{key}")
    return SaaSEvent.attrs[key].astext


def _build_correlation_query(
    *,
    db: AsyncSession,
    org_id: str,
    job_id: Optional[str],
    sheet_id: Optional[str],
    window_start: datetime,
    window_end: datetime,
    ui_session_id: Optional[str] = None,
):
    query = select(SaaSEvent).where(
        SaaSEvent.ts >= window_start,
        SaaSEvent.ts <= window_end,
    )

    if ui_session_id:
        session_expr = _json_attr_expr(db, "ui_session_id")
        seq_expr = cast(_json_attr_expr(db, "ui_seq"), Integer)
        return (
            query.where(session_expr == ui_session_id)
            .order_by(seq_expr.desc())
            .limit(50)
        )

    if job_id:
        query = query.where(SaaSEvent.job_id == job_id)
    elif sheet_id:
        query = query.where(SaaSEvent.org_id == org_id, SaaSEvent.sheet_id == sheet_id)
    else:
        query = query.where(SaaSEvent.org_id == org_id)

    return query.order_by(SaaSEvent.ts.desc()).limit(50)


def _extract_error_code(event: SaaSEvent) -> Optional[str]:
    attrs = event.attrs or {}
    return attrs.get("error_code") or attrs.get("error") or None


def _infer_bucket(error_code: Optional[str]) -> str:
    if not error_code:
        return "unknown"
    code = str(error_code).upper()
    if "AUTH" in code or "TOKEN" in code or "OAUTH" in code:
        return "auth"
    if "VALIDATION" in code or "INVALID" in code or "MISSING" in code:
        return "validation"
    if "CONFIG" in code or "MAPPING" in code:
        return "config"
    if "TIMEOUT" in code or "500" in code or "RATE_LIMIT" in code:
        return "backend"
    return "unknown"


def _format_top_failure(
    failures: List[Dict[str, Any]],
    failure_last_seen: Dict[tuple[str, Optional[str]], datetime],
) -> Optional[Dict[str, Any]]:
    if not failures:
        return None
    top = failures[0]
    key = (top.get("action"), top.get("error_code"))
    last_seen = failure_last_seen.get(key)
    payload = dict(top)
    payload["last_seen"] = last_seen.isoformat() if last_seen else None
    return payload


def _compute_retry_count(events: List[SaaSEvent]) -> int:
    if not events:
        return 0
    first = events[0]
    if first.status != SaaSEventStatus.FAILED:
        return 0
    target = first.action
    count = 0
    for event in events:
        if event.status == SaaSEventStatus.FAILED and event.action == target:
            count += 1
        else:
            break
    return count


def _latest_source(events: List[SaaSEvent]) -> tuple[Optional[str], Optional[str]]:
    for event in events:
        if event.source or event.source_version:
            return event.source_version, event.source
    return None, None


def _last_success_action(events: List[SaaSEvent]) -> Optional[str]:
    for event in events:
        if event.status in SUCCESS_STATUSES:
            return event.action
    return None


async def enrich_ticket_with_telemetry(
    db: AsyncSession,
    org_id: str,
    job_id: Optional[str],
    sheet_id: Optional[str],
    ticket_created_at: datetime,
    window_minutes: int = 30,
    context_json: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
    created_at = _ensure_utc(ticket_created_at)
    window_start = created_at - timedelta(minutes=window_minutes)
    window_end = created_at + timedelta(minutes=5)

    ui_session_id = None
    if context_json:
        ui_session_id = context_json.get("ui_session_id")

    query = _build_correlation_query(
        db=db,
        org_id=org_id,
        job_id=job_id,
        sheet_id=sheet_id,
        window_start=window_start,
        window_end=window_end,
        ui_session_id=ui_session_id,
    )
    result = await db.execute(query)
    events = list(result.scalars().all())

    total_events = len(events)
    failure_events = [event for event in events if event.status == SaaSEventStatus.FAILED]
    failures = len(failure_events)

    action_counts = Counter(event.action for event in events if event.action)
    top_actions_by_count = [
        {"action": action, "count": count}
        for action, count in action_counts.most_common(3)
    ]

    failure_counts = Counter(
        (event.action, _extract_error_code(event)) for event in failure_events
    )
    top_failures_by_count = [
        {"action": action, "error_code": error_code, "count": count}
        for (action, error_code), count in failure_counts.most_common(3)
    ]

    failure_last_seen: Dict[tuple[str, Optional[str]], datetime] = {}
    for event in failure_events:
        key = (event.action, _extract_error_code(event))
        if key not in failure_last_seen or event.ts > failure_last_seen[key]:
            failure_last_seen[key] = event.ts

    retry_count = _compute_retry_count(events)
    top_failure = _format_top_failure(top_failures_by_count, failure_last_seen)
    last_successful_action = _last_success_action(events)
    likely_bucket = _infer_bucket(
        top_failure.get("error_code") if top_failure else None
    )

    source_version, source = _latest_source(events)

    events_payload = []
    for event in events[:25]:
        events_payload.append(
            {
                "ts": event.ts.isoformat() if event.ts else None,
                "action": event.action,
                "status": event.status.value if event.status else None,
                "error_code": _extract_error_code(event),
                "duration_ms": event.duration_ms,
                "job_id": event.job_id,
                "sheet_id": event.sheet_id,
                "source": event.source,
                "source_version": event.source_version,
            }
        )

    bundle = {
        "trace_window": {
            "from": window_start.isoformat(),
            "to": window_end.isoformat(),
        },
        "summary": {
            "total_events": total_events,
            "failures": failures,
            "retry_count": retry_count,
            "likely_bucket": likely_bucket,
            "source_version": source_version,
            "source": source,
            "top_actions_by_count": top_actions_by_count,
            "top_failures_by_count": top_failures_by_count,
        },
        "last_successful_action": last_successful_action,
        "top_failure": top_failure,
        "links": {
            "open_logs": f"/ops/logs?org_id={org_id}&from={window_start.isoformat()}&to={window_end.isoformat()}",
        },
        "events": events_payload,
    }

    return bundle
