# @File: backend/routers/ticket_notifications_sync.py
# @Desc: Ticket event sync for the notification center.
#
# On every call we:
#   1. Pull the user's current ticket list from WHMCS.
#   2. Load the user's previous snapshot from ``ticket_sync_states``.
#   3. Diff the two snapshots and insert ``notifications`` rows for each
#      real change (new ticket, status change, new staff/client reply).
#   4. Persist the new snapshot back to ``ticket_sync_states`` so the next
#      call only sees *new* events.
#
# Notification preferences from ``notification_preferences`` gate which
# events get persisted. The first call for a user just seeds the snapshot
# without emitting notifications (to avoid flooding the bell on first use).
import json
import logging
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from core.database import get_db
from dependencies.auth import get_current_user
from models.notification_preferences import Notification_preferences
from models.notifications import Notifications
from models.ticket_sync_states import Ticket_sync_states
from schemas.auth import UserResponse
from services.whmcs import call_whmcs, get_client_by_email

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v1/notifications", tags=["notifications-sync"])


def _safe_int(v: Any, default: int = 0) -> int:
    try:
        return int(v)
    except (TypeError, ValueError):
        return default


def _as_list(v: Any) -> List[Dict[str, Any]]:
    if v is None:
        return []
    if isinstance(v, list):
        return [x for x in v if isinstance(x, dict)]
    if isinstance(v, dict):
        return [v]
    return []


class SyncResponse(BaseModel):
    created: int
    bootstrapped: bool


async def _get_prefs(db: AsyncSession, user_id: str) -> Dict[str, bool]:
    stmt = select(Notification_preferences).where(
        Notification_preferences.user_id == user_id
    )
    row = (await db.execute(stmt)).scalar_one_or_none()
    if not row:
        # Sensible defaults — everything on, except external email.
        return {
            "ticket_reply": True,
            "ticket_status_change": True,
            "ticket_new": True,
            "email": False,
        }
    return {
        "ticket_reply": bool(row.ticket_reply) if row.ticket_reply is not None else True,
        "ticket_status_change": (
            bool(row.ticket_status_change)
            if row.ticket_status_change is not None
            else True
        ),
        "ticket_new": bool(row.ticket_new) if row.ticket_new is not None else True,
        "email": bool(row.email) if row.email is not None else False,
    }


async def _fetch_user_tickets(email: str) -> List[Dict[str, Any]]:
    """Return a compact list of tickets for the given client email."""
    client = await get_client_by_email(email)
    if not client:
        return []
    client_id = _safe_int(client.get("userid") or client.get("id"))
    if client_id <= 0:
        return []
    try:
        resp = await call_whmcs(
            "GetTickets", {"clientid": client_id, "limitnum": 200}
        )
    except Exception as e:
        logger.warning("sync: GetTickets failed for %s: %s", email, e)
        return []
    raw_items = _as_list((resp.get("tickets") or {}).get("ticket"))
    out: List[Dict[str, Any]] = []
    for raw in raw_items:
        tid = _safe_int(raw.get("id") or raw.get("ticketid"))
        if tid <= 0:
            continue
        out.append({
            "id": tid,
            "tid": str(raw.get("tid") or ""),
            "subject": str(raw.get("subject") or ""),
            "status": str(raw.get("status") or ""),
            "lastreply": str(raw.get("lastreply") or ""),
            "replies": _safe_int(raw.get("replies"), 0),
            "lastreplier": str(
                raw.get("lastreplier")
                or raw.get("lastreply_by")
                or raw.get("adminusername")
                or ""
            ),
        })
    return out


def _diff_snapshots(
    prev: Dict[str, Dict[str, Any]],
    curr: Dict[str, Dict[str, Any]],
    prefs: Dict[str, bool],
) -> List[Dict[str, Any]]:
    """Compute notification payloads for every change between prev and curr."""
    events: List[Dict[str, Any]] = []
    for ticket_id, cur in curr.items():
        prior = prev.get(ticket_id)
        subject = cur.get("subject") or "(no subject)"
        tid_label = cur.get("tid") or ticket_id

        if prior is None:
            if prefs.get("ticket_new"):
                events.append({
                    "type": "ticket_new",
                    "ticket_id": str(ticket_id),
                    "title": f"New ticket #{tid_label} opened",
                    "body": subject,
                    "data": {"status": cur.get("status")},
                })
            continue

        # Status change.
        if (prior.get("status") or "") != (cur.get("status") or ""):
            if prefs.get("ticket_status_change"):
                events.append({
                    "type": "ticket_status_change",
                    "ticket_id": str(ticket_id),
                    "title": f"Ticket #{tid_label} status changed",
                    "body": (
                        f"{prior.get('status') or '—'} → "
                        f"{cur.get('status') or '—'} · {subject}"
                    ),
                    "data": {
                        "from": prior.get("status"),
                        "to": cur.get("status"),
                    },
                })

        # New reply(s). We compare reply count + lastreply timestamp.
        if (
            cur.get("replies", 0) != prior.get("replies", 0)
            or (cur.get("lastreply") or "") != (prior.get("lastreply") or "")
        ):
            if prefs.get("ticket_reply"):
                replier = cur.get("lastreplier") or "Support"
                events.append({
                    "type": "ticket_reply",
                    "ticket_id": str(ticket_id),
                    "title": f"New reply on ticket #{tid_label}",
                    "body": f"{replier} replied · {subject}",
                    "data": {
                        "lastreply": cur.get("lastreply"),
                        "replier": replier,
                    },
                })

    return events


@router.post("/sync-ticket-events", response_model=SyncResponse)
async def sync_ticket_events(
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Compare WHMCS tickets to the last snapshot and persist new notifications."""
    if not current_user.email:
        raise HTTPException(status_code=400, detail="Signed-in user has no email on file.")

    user_id = current_user.id
    prefs = await _get_prefs(db, user_id)

    tickets = await _fetch_user_tickets(current_user.email)
    curr_map: Dict[str, Dict[str, Any]] = {
        str(t["id"]): t for t in tickets
    }

    # Load previous snapshot.
    stmt = select(Ticket_sync_states).where(
        Ticket_sync_states.user_id == user_id
    )
    state = (await db.execute(stmt)).scalar_one_or_none()

    bootstrapped = False
    prev_map: Dict[str, Dict[str, Any]] = {}
    if state and state.snapshot:
        try:
            loaded = json.loads(state.snapshot)
            if isinstance(loaded, dict):
                prev_map = loaded
        except Exception:
            prev_map = {}
    else:
        bootstrapped = True

    created = 0
    if not bootstrapped:
        events = _diff_snapshots(prev_map, curr_map, prefs)
        for ev in events:
            try:
                row = Notifications(
                    user_id=user_id,
                    type=ev["type"],
                    ticket_id=ev.get("ticket_id"),
                    title=ev["title"],
                    body=ev.get("body") or "",
                    data=json.dumps(ev.get("data") or {}),
                    read=False,
                )
                db.add(row)
                created += 1
            except Exception as e:
                logger.warning("Failed to queue notification: %s", e)

    # Persist new snapshot.
    snapshot_json = json.dumps(curr_map)
    try:
        if state is None:
            state = Ticket_sync_states(user_id=user_id, snapshot=snapshot_json)
            db.add(state)
        else:
            state.snapshot = snapshot_json
        await db.commit()
    except Exception as e:
        logger.warning("Failed to persist sync snapshot: %s", e)
        try:
            await db.rollback()
        except Exception:
            pass

    return SyncResponse(created=created, bootstrapped=bootstrapped)