from __future__ import annotations

from datetime import datetime, timedelta
import json
import os
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends, Header, HTTPException, Query
from pydantic import BaseModel, Field
from sqlalchemy import case, func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession

from ...auth_security import AdminContext, require_admin
from ...db import get_session
from ...models import (
    Account,
    InsightTicketSeverity,
    InsightTicketStatus,
    InsightTicketType,
    SaaSEvent,
    SaaSEventStatus,
    TelemetryInsightAction,
    TelemetryInsightSample,
    TelemetryInsightTicket,
    UsageQuotaModel,
    utcnow,
)
from ...services.admin_audit import log_admin_action
from ...services.insight_triage import InsightTriageService
from ...cron.insight_detector_jobs import (
    auto_reopen_tickets,
    auto_resolve_tickets,
    detect_error_first_seen,
    detect_error_rate_regression,
)

router = APIRouter(prefix="/admin/insights", tags=["Admin Insights"])


class InsightTicketListItem(BaseModel):
    id: str
    title: str
    status: str
    severity: str
    type: str
    feature: Optional[str] = None
    action: Optional[str] = None
    error_code: Optional[str] = None
    event_count: int
    affected_orgs: int
    affected_paying_orgs: int
    first_seen_at: datetime
    last_seen_at: datetime
    severity_reasons: List[str] = Field(default_factory=list)


class InsightTicketActionItem(BaseModel):
    id: int
    action_type: str
    actor_email: Optional[str] = None
    details: Optional[Dict[str, Any]] = None
    created_at: datetime


class InsightSampleEvent(BaseModel):
    id: str
    action: str
    status: str
    duration_ms: Optional[int] = None
    source_version: Optional[str] = None
    ts: datetime
    error_code: Optional[str] = None
    error_stage: Optional[str] = None
    error_hint: Optional[str] = None


class InsightTicketDetail(BaseModel):
    id: str
    title: str
    status: str
    severity: str
    type: str
    feature: Optional[str] = None
    action: Optional[str] = None
    error_code: Optional[str] = None
    source_version: Optional[str] = None
    event_count: int
    affected_orgs: int
    affected_paying_orgs: int
    first_seen_at: datetime
    last_seen_at: datetime
    snoozed_until: Optional[datetime] = None
    known_since: Optional[datetime] = None
    closed_at: Optional[datetime] = None
    closed_by: Optional[str] = None
    github_issue_url: Optional[str] = None
    github_issue_linked_at: Optional[datetime] = None
    fixed_version: Optional[str] = None
    fixed_at: Optional[datetime] = None
    fixed_by: Optional[str] = None
    fixed_pr_url: Optional[str] = None
    context_json: Dict[str, Any]
    primary_org_id: Optional[str] = None
    affected_org_ids: List[str] = Field(default_factory=list)
    severity_reasons: List[str] = Field(default_factory=list)


class InsightTicketListResponse(BaseModel):
    tickets: List[InsightTicketListItem]
    pagination: Dict[str, Any]


class InsightTicketDetailResponse(BaseModel):
    ticket: InsightTicketDetail
    actions: List[InsightTicketActionItem]
    samples: List[InsightSampleEvent]
    affected_accounts: List[Dict[str, Any]]


class InsightTicketUpdate(BaseModel):
    status: Optional[InsightTicketStatus] = None
    severity: Optional[InsightTicketSeverity] = None
    snoozed_until: Optional[datetime] = None
    known_since: Optional[datetime] = None
    closed_at: Optional[datetime] = None
    github_issue_url: Optional[str] = None
    fixed_version: Optional[str] = None
    fixed_pr_url: Optional[str] = None


class InsightTriageRequest(BaseModel):
    force: bool = False


class GithubIssueRequest(BaseModel):
    mode: str
    url: Optional[str] = None
    draft: Optional[Dict[str, Any]] = None


class InsightActionCreate(BaseModel):
    action_type: str
    details: Optional[Dict[str, Any]] = None


_SEVERITY_SORT = case(
    (TelemetryInsightTicket.severity == InsightTicketSeverity.CRITICAL, 4),
    (TelemetryInsightTicket.severity == InsightTicketSeverity.HIGH, 3),
    (TelemetryInsightTicket.severity == InsightTicketSeverity.MEDIUM, 2),
    (TelemetryInsightTicket.severity == InsightTicketSeverity.LOW, 1),
    else_=0,
)


def _parse_csv(value: Optional[str]) -> Optional[List[str]]:
    if not value:
        return None
    items = [item.strip() for item in value.split(",") if item and item.strip()]
    return items or None


def _parse_enum_list(
    raw: Optional[List[str]], enum_cls, param_name: str
) -> Optional[List[Any]]:
    if not raw:
        return None
    values = []
    invalid = []
    for item in raw:
        item_value = item.strip().lower() if isinstance(item, str) else item
        try:
            values.append(enum_cls(item_value))
        except Exception:
            invalid.append(item)
    if invalid:
        raise HTTPException(
            status_code=400,
            detail={
                "error": "INVALID_FILTER",
                "message": f"Unsupported {param_name}: {', '.join(invalid)}",
            },
        )
    return values or None


def _strip_feature_prefix(action: Optional[str], feature: Optional[str]) -> Optional[str]:
    if not action or not feature:
        return action
    prefix = f"{feature}_"
    return action[len(prefix) :] if action.startswith(prefix) else action


def _safe_attrs(raw: Any) -> Dict[str, Any]:
    if isinstance(raw, dict):
        return raw
    if isinstance(raw, str):
        try:
            return json.loads(raw)
        except Exception:
            return {}
    return {}


@router.get("", response_model=Dict[str, Any])
async def list_insights(
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
    status: Optional[str] = Query(None, description="CSV status filter"),
    severity: Optional[str] = Query(None, description="CSV severity filter"),
    type: Optional[str] = Query(None, description="CSV type filter"),
    feature: Optional[str] = Query(None, description="CSV feature filter"),
    search: Optional[str] = Query(None, description="Title or error_code search"),
    sort: str = Query("last_seen", description="last_seen|severity|event_count"),
    direction: str = Query("desc", description="asc|desc"),
    page: int = Query(1, ge=1),
    limit: int = Query(50, ge=1, le=200),
) -> Dict[str, Any]:
    status_list = _parse_enum_list(_parse_csv(status), InsightTicketStatus, "status")
    severity_list = _parse_enum_list(
        _parse_csv(severity), InsightTicketSeverity, "severity"
    )
    type_list = _parse_enum_list(_parse_csv(type), InsightTicketType, "type")
    features = _parse_csv(feature)

    filters = []
    if status_list:
        filters.append(TelemetryInsightTicket.status.in_(status_list))
    if severity_list:
        filters.append(TelemetryInsightTicket.severity.in_(severity_list))
    if type_list:
        filters.append(TelemetryInsightTicket.type.in_(type_list))
    if features:
        filters.append(TelemetryInsightTicket.feature.in_(features))

    if search:
        like = f"%{search}%"
        filters.append(
            or_(
                TelemetryInsightTicket.title.ilike(like),
                TelemetryInsightTicket.error_code.ilike(like),
            )
        )

    count_stmt = select(func.count(TelemetryInsightTicket.id))
    for clause in filters:
        count_stmt = count_stmt.where(clause)
    total = int((await db.execute(count_stmt)).scalar_one() or 0)

    stmt = select(TelemetryInsightTicket)
    for clause in filters:
        stmt = stmt.where(clause)

    order_column = TelemetryInsightTicket.last_seen_at
    if sort == "severity":
        order_column = _SEVERITY_SORT
    elif sort == "event_count":
        order_column = TelemetryInsightTicket.event_count

    order = (
        order_column.asc()
        if direction.lower() == "asc"
        else order_column.desc()
    )

    stmt = stmt.order_by(order).offset((page - 1) * limit).limit(limit)
    rows = (await db.execute(stmt)).scalars().all()

    tickets = [
        InsightTicketListItem(
            id=str(row.id),
            title=row.title,
            status=row.status.value if row.status else "unknown",
            severity=row.severity.value if row.severity else "unknown",
            type=row.type.value if row.type else "unknown",
            feature=row.feature,
            action=_strip_feature_prefix(row.action, row.feature),
            error_code=row.error_code,
            event_count=row.event_count or 0,
            affected_orgs=row.affected_orgs or 0,
            affected_paying_orgs=row.affected_paying_orgs or 0,
            first_seen_at=row.first_seen_at,
            last_seen_at=row.last_seen_at,
            severity_reasons=list(
                (row.context_json or {}).get("severity_reasons") or []
            ),
        )
        for row in rows
    ]

    await log_admin_action(
        admin_user_id=admin_ctx.user_id,
        admin_email=admin_ctx.email,
        action="list_insight_tickets",
        db=db,
        details={
            "page": page,
            "limit": limit,
            "status": status,
            "severity": severity,
            "type": type,
            "feature": feature,
            "search": search,
        },
    )

    response = InsightTicketListResponse(
        tickets=tickets,
        pagination={
            "page": page,
            "limit": limit,
            "total": total,
            "pages": (total + limit - 1) // limit,
        },
    )
    return {"ok": True, "data": response.model_dump()}


@router.get("/{ticket_id}", response_model=Dict[str, Any])
async def get_insight_detail(
    ticket_id: str,
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    ticket = await db.get(TelemetryInsightTicket, ticket_id)
    if not ticket:
        raise HTTPException(404, "Insight ticket not found")

    action_stmt = (
        select(TelemetryInsightAction)
        .where(TelemetryInsightAction.ticket_id == ticket.id)
        .order_by(TelemetryInsightAction.created_at.desc())
    )
    actions = (await db.execute(action_stmt)).scalars().all()

    sample_stmt = (
        select(SaaSEvent)
        .join(
            TelemetryInsightSample,
            TelemetryInsightSample.saas_event_id == SaaSEvent.id,
        )
        .where(TelemetryInsightSample.ticket_id == ticket.id)
        .order_by(SaaSEvent.ts.desc())
        .limit(10)
    )
    sample_events = (await db.execute(sample_stmt)).scalars().all()

    affected_accounts: List[Dict[str, Any]] = []
    affected_ids = list(ticket.affected_org_ids or [])
    last_seen_map: Dict[str, datetime] = {}
    if affected_ids:
        event_stmt = (
            select(SaaSEvent.org_id, SaaSEvent.ts, SaaSEvent.attrs)
            .where(SaaSEvent.org_id.in_(affected_ids))
            .where(SaaSEvent.status == SaaSEventStatus.FAILED)
        )
        if ticket.first_seen_at:
            event_stmt = event_stmt.where(SaaSEvent.ts >= ticket.first_seen_at)
        if ticket.action:
            event_stmt = event_stmt.where(SaaSEvent.action == ticket.action)
        event_rows = (await db.execute(event_stmt)).all()
        for org_id, ts, attrs in event_rows:
            if ticket.error_code:
                attr_map = _safe_attrs(attrs)
                if attr_map.get("error_code") != ticket.error_code:
                    continue
            if org_id and ts:
                prev = last_seen_map.get(org_id)
                if not prev or ts > prev:
                    last_seen_map[org_id] = ts

        account_rows = (
            await db.execute(
                select(Account, UsageQuotaModel)
                .outerjoin(
                    UsageQuotaModel, UsageQuotaModel.account_id == Account.id
                )
                .where(Account.id.in_(affected_ids))
            )
        ).all()
        affected_accounts = [
            {
                "id": account.id,
                "name": account.name,
                "tier": quota.tier if quota else None,
                "last_seen_at": last_seen_map.get(account.id),
            }
            for account, quota in account_rows
        ]
        affected_accounts.sort(
            key=lambda row: row.get("last_seen_at") or datetime.min,
            reverse=True,
        )

    detail = InsightTicketDetail(
        id=str(ticket.id),
        title=ticket.title,
        status=ticket.status.value if ticket.status else "unknown",
        severity=ticket.severity.value if ticket.severity else "unknown",
        type=ticket.type.value if ticket.type else "unknown",
        feature=ticket.feature,
        action=_strip_feature_prefix(ticket.action, ticket.feature),
        error_code=ticket.error_code,
        source_version=ticket.source_version,
        event_count=ticket.event_count or 0,
        affected_orgs=ticket.affected_orgs or 0,
        affected_paying_orgs=ticket.affected_paying_orgs or 0,
        first_seen_at=ticket.first_seen_at,
        last_seen_at=ticket.last_seen_at,
        snoozed_until=ticket.snoozed_until,
        known_since=ticket.known_since,
        closed_at=ticket.closed_at,
        closed_by=ticket.closed_by,
        github_issue_url=ticket.github_issue_url,
        github_issue_linked_at=ticket.github_issue_linked_at,
        fixed_version=ticket.fixed_version,
        fixed_at=ticket.fixed_at,
        fixed_by=ticket.fixed_by,
        fixed_pr_url=ticket.fixed_pr_url,
        context_json=ticket.context_json or {},
        primary_org_id=ticket.primary_org_id,
        affected_org_ids=affected_ids,
        severity_reasons=list(
            (ticket.context_json or {}).get("severity_reasons") or []
        ),
    )

    sample_payload: List[InsightSampleEvent] = []
    for event in sample_events:
        attrs = _safe_attrs(event.attrs)
        sample_payload.append(
            InsightSampleEvent(
                id=str(event.id),
                action=event.action,
                status=event.status.value if event.status else "unknown",
                duration_ms=event.duration_ms,
                source_version=event.source_version,
                ts=event.ts,
                error_code=attrs.get("error_code"),
                error_stage=attrs.get("error_stage"),
                error_hint=attrs.get("error_hint"),
            )
        )

    response = InsightTicketDetailResponse(
        ticket=detail,
        actions=[
            InsightTicketActionItem(
                id=action.id,
                action_type=action.action_type,
                actor_email=action.actor_email,
                details=action.details,
                created_at=action.created_at,
            )
            for action in actions
        ],
        samples=sample_payload,
        affected_accounts=affected_accounts,
    )

    await log_admin_action(
        admin_user_id=admin_ctx.user_id,
        admin_email=admin_ctx.email,
        action="view_insight_ticket",
        db=db,
        target_account_id=ticket.primary_org_id,
        details={"ticket_id": str(ticket.id)},
    )

    return {"ok": True, "data": response.model_dump()}


@router.patch("/{ticket_id}", response_model=Dict[str, Any])
async def update_insight_ticket(
    ticket_id: str,
    payload: InsightTicketUpdate,
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    ticket = await db.get(TelemetryInsightTicket, ticket_id)
    if not ticket:
        raise HTTPException(404, "Insight ticket not found")

    now = utcnow()
    action_type = "updated"
    details: Dict[str, Any] = {}

    if payload.status:
        ticket.status = payload.status
        if payload.status == InsightTicketStatus.SNOOZED:
            ticket.snoozed_until = payload.snoozed_until or (now + timedelta(days=7))
            action_type = "snoozed"
            details["snoozed_until"] = ticket.snoozed_until.isoformat()
        elif payload.status == InsightTicketStatus.KNOWN:
            ticket.known_since = payload.known_since or now
            action_type = "marked_known"
            details["known_since"] = ticket.known_since.isoformat()
        elif payload.status == InsightTicketStatus.CLOSED:
            ticket.closed_at = payload.closed_at or now
            ticket.closed_by = admin_ctx.email
            action_type = "closed"
            details["closed_at"] = ticket.closed_at.isoformat()

    if payload.severity:
        ticket.severity = payload.severity
        details["severity"] = payload.severity.value
        if action_type == "updated":
            action_type = "severity_updated"

    if payload.github_issue_url:
        ticket.github_issue_url = payload.github_issue_url
        ticket.github_issue_linked_at = now
        details["github_issue_url"] = payload.github_issue_url
        if action_type == "updated":
            action_type = "github_issue_linked"

    if payload.fixed_version:
        ticket.fixed_version = payload.fixed_version
        ticket.fixed_at = now
        ticket.fixed_by = admin_ctx.email
        details["fixed_version"] = payload.fixed_version
        if action_type == "updated":
            action_type = "fixed_version_set"

    if payload.fixed_pr_url:
        ticket.fixed_pr_url = payload.fixed_pr_url
        details["fixed_pr_url"] = payload.fixed_pr_url
        if action_type == "updated":
            action_type = "fixed_pr_linked"

    ticket.updated_at = now
    db.add(ticket)
    db.add(
        TelemetryInsightAction(
            ticket_id=ticket.id,
            action_type=action_type,
            actor_email=admin_ctx.email,
            details=details or None,
            created_at=now,
        )
    )
    await db.commit()

    await log_admin_action(
        admin_user_id=admin_ctx.user_id,
        admin_email=admin_ctx.email,
        action="update_insight_ticket",
        db=db,
        target_account_id=ticket.primary_org_id,
        details={"ticket_id": str(ticket.id), "action_type": action_type},
    )

    return {"ok": True, "data": {"ticket_id": str(ticket.id)}}


@router.post("/{ticket_id}/ai-triage", response_model=Dict[str, Any])
async def run_insight_triage(
    ticket_id: str,
    request: InsightTriageRequest,
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    ticket = await db.get(TelemetryInsightTicket, ticket_id)
    if not ticket:
        raise HTTPException(404, "Insight ticket not found")

    sample_stmt = (
        select(SaaSEvent)
        .join(
            TelemetryInsightSample,
            TelemetryInsightSample.saas_event_id == SaaSEvent.id,
        )
        .where(TelemetryInsightSample.ticket_id == ticket.id)
        .order_by(SaaSEvent.ts.desc())
        .limit(10)
    )
    sample_events = (await db.execute(sample_stmt)).scalars().all()

    try:
        service = InsightTriageService()
    except Exception as exc:
        raise HTTPException(503, f"AI triage unavailable: {exc}")

    try:
        triage_result, context_hash, cached = await service.run_triage(
            db=db,
            ticket=ticket,
            sample_events=sample_events,
            force=request.force,
        )
    except Exception as exc:
        message = str(exc).lower()
        if "reauthentication" in message or "application-default login" in message:
            raise HTTPException(
                503,
                "Vertex AI auth required. Run `gcloud auth application-default login`.",
            )
        if isinstance(exc, TimeoutError) or "timed out" in message:
            raise HTTPException(
                504,
                "Vertex AI request timed out. Try again later.",
            )
        raise HTTPException(500, "AI triage failed")

    now = utcnow()
    ticket.ai_triage_hash = context_hash
    ticket.ai_triage_last_run_at = now
    db.add(ticket)
    if not cached:
        db.add(
            TelemetryInsightAction(
                ticket_id=ticket.id,
                action_type="ai_triage",
                actor_email="ai@foundryops.io",
                details={"context_hash": context_hash, "result": triage_result},
                created_at=now,
            )
        )
    await db.commit()

    await log_admin_action(
        admin_user_id=admin_ctx.user_id,
        admin_email=admin_ctx.email,
        action="run_insight_triage",
        db=db,
        target_account_id=ticket.primary_org_id,
        details={"ticket_id": str(ticket.id), "cached": cached},
    )

    return {"ok": True, "data": triage_result, "cached": cached}


@router.post("/{ticket_id}/github-issue", response_model=Dict[str, Any])
async def link_github_issue(
    ticket_id: str,
    request: GithubIssueRequest,
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    ticket = await db.get(TelemetryInsightTicket, ticket_id)
    if not ticket:
        raise HTTPException(404, "Insight ticket not found")

    mode = (request.mode or "").lower()
    if mode not in {"link", "create"}:
        raise HTTPException(400, "mode must be link or create")

    now = utcnow()
    action_type = "github_issue_linked" if mode == "link" else "github_issue_created"

    if mode == "link":
        if not request.url:
            raise HTTPException(400, "url is required for link mode")
        ticket.github_issue_url = request.url
        ticket.github_issue_linked_at = now
        db.add(ticket)

    db.add(
        TelemetryInsightAction(
            ticket_id=ticket.id,
            action_type=action_type,
            actor_email=admin_ctx.email,
            details={"url": request.url, "draft": request.draft, "mode": mode},
            created_at=now,
        )
    )
    await db.commit()

    await log_admin_action(
        admin_user_id=admin_ctx.user_id,
        admin_email=admin_ctx.email,
        action="github_issue_action",
        db=db,
        target_account_id=ticket.primary_org_id,
        details={"ticket_id": str(ticket.id), "mode": mode},
    )

    return {"ok": True, "data": {"ticket_id": str(ticket.id), "mode": mode}}


@router.post("/{ticket_id}/actions", response_model=Dict[str, Any])
async def log_insight_action(
    ticket_id: str,
    payload: InsightActionCreate,
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    ticket = await db.get(TelemetryInsightTicket, ticket_id)
    if not ticket:
        raise HTTPException(404, "Insight ticket not found")

    if not payload.action_type:
        raise HTTPException(400, "action_type is required")

    action = TelemetryInsightAction(
        ticket_id=ticket.id,
        action_type=payload.action_type,
        actor_email=admin_ctx.email,
        details=payload.details,
        created_at=utcnow(),
    )
    db.add(action)
    await db.commit()

    await log_admin_action(
        admin_user_id=admin_ctx.user_id,
        admin_email=admin_ctx.email,
        action="log_insight_action",
        db=db,
        target_account_id=ticket.primary_org_id,
        details={"ticket_id": str(ticket.id), "action_type": payload.action_type},
    )

    return {"ok": True, "data": {"action_id": action.id}}


@router.post("/detect/run-all", response_model=Dict[str, Any])
async def run_insight_detectors(
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
    x_insight_secret: Optional[str] = Header(None, alias="X-Insight-Secret"),
) -> Dict[str, Any]:
    required_secret = os.getenv("INSIGHT_DETECTOR_SECRET")
    if required_secret and x_insight_secret != required_secret:
        raise HTTPException(403, "Invalid detector secret")

    await detect_error_first_seen(db)
    await detect_error_rate_regression(db)
    await auto_resolve_tickets(db)
    await auto_reopen_tickets(db)

    await log_admin_action(
        admin_user_id=admin_ctx.user_id,
        admin_email=admin_ctx.email,
        action="run_insight_detectors",
        db=db,
        details={"mode": "run-all"},
    )

    return {"ok": True, "data": {"status": "completed"}}
