"""Assist / takeover API — Spec 010 Phase A."""

from __future__ import annotations

import os
import secrets
from datetime import datetime, timezone

import httpx
from fastapi import APIRouter, Depends, HTTPException

from app import assist_catalog, assist_store, auth
from app.permissions import (
    can_assist_handoff,
    can_assist_takeover,
    can_read_assist,
)

router = APIRouter(prefix="/api/v1/assist", tags=["assist"])

VM112_ASSIST_API = os.getenv("VM112_ASSIST_API_URL", os.getenv("VM112_API_URL", "http://10.10.10.112:8090"))
VM112_ASSIST_TOKEN = os.getenv("VM112_ASSIST_SERVICE_TOKEN", "")
DESK_ASSIST_ENABLED = os.getenv("DESK_ASSIST_ENABLED", "true").lower() in ("1", "true", "yes")
VM112_ASSIST_CALL = os.getenv("VM112_ASSIST_CALL_VM112", "false").lower() in ("1", "true", "yes")


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _main():
    from app import main as m

    return m


def _session_meta(conn, session_id: str) -> dict:
    m = _main()
    meta = assist_store.session_funnel_meta(
        conn, session_id, m.FUNNEL_EVENT_RANK, m.FUNNEL_STAGE_BY_RANK, m.ONBOARD_SOURCE
    )
    if not meta.get("session_id"):
        raise HTTPException(404, "sessão não encontrada no funil")
    if meta["funnel_rank"] == 0 and not meta.get("last_event_at"):
        raise HTTPException(404, "sessão não encontrada no funil")
    return meta


def _parse_payload(raw: str | None) -> dict:
    m = _main()
    return m._parse_payload(raw)


def _build_session_view(conn, session_id: str, user: auth.DeskUser) -> dict:
    m = _main()
    meta = _session_meta(conn, session_id)
    assist = assist_store.get_active_assist(conn, session_id)
    ticket_row = assist_store.find_ticket_by_session(conn, session_id)
    ticket = None
    if ticket_row:
        full = conn.execute(
            f"SELECT {m.TICKET_COLUMNS}, session_id, assist_mode, assisted_by, assisted_at, client_paused FROM tickets WHERE id = ?",
            (int(ticket_row["id"]),),
        ).fetchone()
        if full:
            ticket = m._visible_ticket(m._enrich_ticket(full), user)
    view = {
        **meta,
        "assist_status": assist["status"] if assist else None,
        "assist_session_id": assist["id"] if assist else None,
        "assisted_by": assist.get("initiated_by_user") if assist else (ticket or {}).get("assisted_by"),
        "ticket_id": int(ticket_row["id"]) if ticket_row else None,
        "ticket_status": ticket_row["status"] if ticket_row else None,
        "assigned_to": ticket_row["assigned_to"] if ticket_row else None,
        "can_takeover": meta["can_escalate"] and can_assist_takeover(user.role) and not (assist and assist.get("status") == "active"),
    }
    if ticket:
        view["ticket"] = ticket
    return view


def _call_vm112(
    method: str,
    path: str,
    json_body: dict | None = None,
    session_id: str | None = None,
) -> dict | None:
    if not VM112_ASSIST_CALL:
        return None
    headers: dict[str, str] = {}
    if VM112_ASSIST_TOKEN:
        headers["X-Desk-Assist-Token"] = VM112_ASSIST_TOKEN
    if session_id:
        headers["X-Onboarding-Session"] = session_id
    try:
        with httpx.Client(timeout=30.0) as client:
            r = client.request(method, f"{VM112_ASSIST_API.rstrip('/')}{path}", json=json_body, headers=headers)
            if r.status_code >= 400:
                return {"error": r.text[:200], "http_status": r.status_code}
            return r.json() if r.headers.get("content-type", "").startswith("application/json") else {"ok": True}
    except Exception as exc:
        return {"error": str(exc)}


@router.get("/sessions")
def list_assist_sessions(user: auth.DeskUser = Depends(auth.get_current_user)):
    if not DESK_ASSIST_ENABLED:
        raise HTTPException(503, "assistência desactivada")
    if not can_read_assist(user.role):
        raise HTTPException(403, "permissão insuficiente")
    m = _main()
    with m.db() as conn:
        funnel = m._funnel_summary(conn, window_hours=48)
        session_ids = [s["session_id"] for s in funnel.get("active_sessions", []) if s.get("session_id")]
        assist_map = assist_store.get_assist_state_map(conn, session_ids)
        sessions = []
        for item in funnel.get("active_sessions", []):
            sid = item.get("session_id")
            if not sid:
                continue
            assist = assist_map.get(sid)
            ticket_row = assist_store.find_ticket_by_session(conn, sid)
            status = "observing"
            if assist and assist.get("status") == "active":
                status = "assisting"
            elif ticket_row and ticket_row["status"] in ("escalated", "assisting"):
                status = ticket_row["status"]
            meta = assist_store.session_funnel_meta(
                conn, sid, m.FUNNEL_EVENT_RANK, m.FUNNEL_STAGE_BY_RANK, m.ONBOARD_SOURCE
            )
            sessions.append({
                **item,
                "assist_status": status,
                "assisted_by": assist.get("initiated_by_user") if assist else None,
                "assigned_to": ticket_row["assigned_to"] if ticket_row else None,
                "can_escalate": meta.get("can_escalate", False),
            })
    return {"sessions": sessions, "window_hours": funnel.get("window_hours", 48)}


@router.get("/sessions/{session_id}")
def get_assist_session(session_id: str, user: auth.DeskUser = Depends(auth.get_current_user)):
    if not can_read_assist(user.role):
        raise HTTPException(403, "permissão insuficiente")
    sid = session_id.strip()
    m = _main()
    with m.db() as conn:
        view = _build_session_view(conn, sid, user)
        timeline = m._session_timeline(conn, sid)
        from app.funnel_timing import apply_module_timing

        enriched, timing_meta = apply_module_timing(timeline)
        view["timeline"] = enriched
        if timing_meta:
            view["timing"] = timing_meta
        actions = []
        if view.get("assist_session_id"):
            rows = conn.execute(
                """
                SELECT actor, action, payload, created_at
                FROM assist_actions WHERE assist_session_id = ?
                ORDER BY id ASC LIMIT 50
                """,
                (view["assist_session_id"],),
            ).fetchall()
            actions = [
                {
                    "actor": r["actor"],
                    "action": r["action"],
                    "payload": _parse_payload(r["payload"]),
                    "created_at": r["created_at"],
                }
                for r in rows
            ]
        view["actions"] = actions
    return view


@router.post("/sessions/{session_id}/escalate")
def escalate_session(session_id: str, user: auth.DeskUser = Depends(auth.get_current_user)):
    if not can_assist_takeover(user.role):
        raise HTTPException(403, "permissão insuficiente")
    sid = session_id.strip()
    now = _now()
    m = _main()
    with m.db() as conn:
        meta = _session_meta(conn, sid)
        if not meta["can_escalate"]:
            raise HTTPException(400, "escalada só após validação de domínio")
        active = assist_store.get_active_assist(conn, sid)
        open_assist = assist_store.get_open_assist(conn, sid)
        if active:
            raise HTTPException(
                409,
                detail={
                    "message": "sessão já em assistência",
                    "assisted_by": active.get("initiated_by_user"),
                },
            )
        if open_assist and open_assist.get("status") == "escalated" and open_assist.get("initiated_by_user") not in (None, user.username):
            raise HTTPException(
                409,
                detail={
                    "message": "sessão já escalada por outro técnico",
                    "assisted_by": open_assist.get("initiated_by_user"),
                },
            )
        ticket_id = assist_store.ensure_onboard_ticket(conn, sid, meta.get("domain"))
        conn.execute(
            """
            UPDATE tickets
            SET status = 'escalated', assigned_to = ?, assigned_at = ?, session_id = ?
            WHERE id = ?
            """,
            (user.username, now, sid, ticket_id),
        )
        if open_assist and open_assist.get("status") == "escalated":
            assist_id = int(open_assist["id"])
            conn.execute(
                "UPDATE assist_sessions SET initiated_by_user = ?, initiated_by = 'technician' WHERE id = ?",
                (user.username, assist_id),
            )
        else:
            cur = conn.execute(
                """
                INSERT INTO assist_sessions
                (session_id, ticket_id, initiated_by, initiated_by_user, status, funnel_stage, domain, started_at)
                VALUES (?, ?, 'technician', ?, 'escalated', ?, ?, ?)
                """,
                (sid, ticket_id, user.username, meta.get("funnel_stage"), meta.get("domain"), now),
            )
            assist_id = int(cur.lastrowid)
        assist_store.log_action(conn, assist_id, user.username, "escalate", {"source": "desk"})
        conn.commit()
        view = _build_session_view(conn, sid, user)
    return {"status": "escalated", "ticket_id": ticket_id, "session": view}


@router.post("/sessions/{session_id}/takeover")
def takeover_session(session_id: str, user: auth.DeskUser = Depends(auth.get_current_user)):
    if not can_assist_takeover(user.role):
        raise HTTPException(403, "permissão insuficiente")
    sid = session_id.strip()
    now = _now()
    m = _main()
    with m.db() as conn:
        meta = _session_meta(conn, sid)
        if not meta["can_escalate"]:
            raise HTTPException(400, "takeover só após validação de domínio")
        active = assist_store.get_active_assist(conn, sid)
        open_assist = assist_store.get_open_assist(conn, sid)
        if active and active.get("initiated_by_user") not in (None, user.username):
            raise HTTPException(
                409,
                detail={
                    "message": "sessão já assumida por outro técnico",
                    "assisted_by": active.get("initiated_by_user"),
                },
            )
        ticket_id = assist_store.ensure_onboard_ticket(conn, sid, meta.get("domain"))
        token_hash = assist_store.hash_token(secrets.token_urlsafe(32))

        if open_assist and open_assist.get("status") == "escalated":
            assist_id = int(open_assist["id"])
            conn.execute(
                """
                UPDATE assist_sessions
                SET status = 'active', initiated_by_user = ?, takeover_token_hash = ?
                WHERE id = ?
                """,
                (user.username, token_hash, assist_id),
            )
        elif active:
            assist_id = int(active["id"])
        else:
            cur = conn.execute(
                """
                INSERT INTO assist_sessions
                (session_id, ticket_id, initiated_by, initiated_by_user, status, funnel_stage, domain,
                 takeover_token_hash, started_at)
                VALUES (?, ?, 'technician', ?, 'active', ?, ?, ?, ?)
                """,
                (sid, ticket_id, user.username, meta.get("funnel_stage"), meta.get("domain"), token_hash, now),
            )
            assist_id = int(cur.lastrowid)

        conn.execute(
            """
            UPDATE tickets
            SET status = 'assisting', assigned_to = ?, assigned_at = ?, session_id = ?,
                assist_mode = 'asm', assisted_by = ?, assisted_at = ?, client_paused = 1
            WHERE id = ?
            """,
            (user.username, now, sid, user.username, now, ticket_id),
        )
        assist_store.log_action(conn, assist_id, user.username, "takeover", {"phase": "A"})
        conn.commit()

    _call_vm112("POST", f"/onboarding/sessions/{sid}/pause", session_id=sid)
    vm112_result = _call_vm112(
        "POST",
        f"/onboarding/sessions/{sid}/takeover",
        {"technician": user.username},
        session_id=sid,
    )
    if vm112_result and vm112_result.get("takeover_url"):
        takeover_url = vm112_result["takeover_url"]
    else:
        base = os.getenv("VM112_WIZARD_URL", "https://onboard.ibytera.com")
        takeover_url = f"{base.rstrip('/')}/assist/{sid}?desk=1"

    return {
        "status": "assisting",
        "ticket_id": ticket_id,
        "takeover_url": takeover_url,
        "client_paused": True,
        "vm112": vm112_result,
        "note": "ASM activo — wizard VM112 pausado para o cliente",
    }


@router.post("/sessions/{session_id}/handoff")
def handoff_session(session_id: str, user: auth.DeskUser = Depends(auth.get_current_user)):
    if not can_assist_handoff(user.role, user.username):
        raise HTTPException(403, "permissão insuficiente")
    sid = session_id.strip()
    now = _now()
    m = _main()
    with m.db() as conn:
        active = assist_store.get_active_assist(conn, sid)
        if not active:
            raise HTTPException(404, "nenhuma assistência activa")
        if user.role == "technician" and active.get("initiated_by_user") not in (None, user.username):
            raise HTTPException(403, "sessão de outro técnico")
        ticket_row = assist_store.find_ticket_by_session(conn, sid)
        ticket_id = int(ticket_row["id"]) if ticket_row else active.get("ticket_id")
        conn.execute(
            "UPDATE assist_sessions SET status = 'ended', ended_at = ? WHERE id = ?",
            (now, int(active["id"])),
        )
        if ticket_id:
            conn.execute(
                """
                UPDATE tickets
                SET status = 'resolved', client_paused = 0, assist_mode = NULL
                WHERE id = ?
                """,
                (ticket_id,),
            )
        assist_store.log_action(conn, int(active["id"]), user.username, "handoff", {})
        conn.commit()
    _call_vm112("POST", f"/onboarding/sessions/{sid}/resume", session_id=sid)
    return {"status": "handoff", "ticket_id": ticket_id}


@router.get("/sessions/{session_id}/links")
def session_external_links(session_id: str, user: auth.DeskUser = Depends(auth.get_current_user)):
    if not can_read_assist(user.role):
        raise HTTPException(403, "permissão insuficiente")
    sid = session_id.strip()
    m = _main()
    with m.db() as conn:
        meta = _session_meta(conn, sid)
    return {
        "session_id": sid,
        "domain": meta.get("domain"),
        "links": assist_catalog.external_links(meta.get("domain")),
    }


@router.get("/sessions/{session_id}/actions")
def list_session_actions(session_id: str, user: auth.DeskUser = Depends(auth.get_current_user)):
    if not can_read_assist(user.role):
        raise HTTPException(403, "permissão insuficiente")
    sid = session_id.strip()
    m = _main()
    with m.db() as conn:
        meta = _session_meta(conn, sid)
        assist = assist_store.get_active_assist(conn, sid)
        ticket_row = assist_store.find_ticket_by_session(conn, sid)
        is_assisting = bool(assist and assist.get("status") == "active") or (
            ticket_row and ticket_row["status"] == "assisting"
        )
        actions = assist_catalog.list_actions_for_session(
            meta.get("funnel_stage"), is_assisting, user.role
        )
    return {"session_id": sid, "actions": actions, "is_assisting": is_assisting}


@router.post("/sessions/{session_id}/actions/{action_id}")
def run_session_action(
    session_id: str,
    action_id: str,
    user: auth.DeskUser = Depends(auth.get_current_user),
):
    if not can_assist_takeover(user.role):
        raise HTTPException(403, "permissão insuficiente")
    sid = session_id.strip()
    action = action_id.strip()
    now = _now()
    m = _main()
    with m.db() as conn:
        meta = _session_meta(conn, sid)
        domain = (meta.get("domain") or "").strip()
        if not domain:
            raise HTTPException(400, "domínio não disponível na sessão")
        assist = assist_store.get_active_assist(conn, sid)
        open_assist = assist_store.get_open_assist(conn, sid)
        assist_id = int(assist["id"]) if assist else (int(open_assist["id"]) if open_assist else None)
        ticket_row = assist_store.find_ticket_by_session(conn, sid)
        ticket_payload = _parse_payload(ticket_row["payload"]) if ticket_row else {}
        is_assisting = bool(assist and assist.get("status") == "active")

        if action == assist_catalog.ABORT_ACTION:
            if user.role not in ("super_admin", "ops_lead"):
                raise HTTPException(403, "abortar só ops_lead+")
            if ticket_row:
                conn.execute(
                    "UPDATE tickets SET status = 'closed', client_paused = 0, assist_mode = NULL WHERE id = ?",
                    (int(ticket_row["id"]),),
                )
            if assist:
                conn.execute(
                    "UPDATE assist_sessions SET status = 'ended', ended_at = ? WHERE id = ?",
                    (now, int(assist["id"])),
                )
            if assist_id:
                assist_store.log_action(conn, assist_id, user.username, f"action.{action}", {"domain": domain})
            conn.commit()
            _call_vm112("POST", f"/onboarding/sessions/{sid}/resume", session_id=sid)
            return {"status": "aborted", "action": action, "session_id": sid}

        if action == assist_catalog.MARK_STEP_ACTION:
            if not is_assisting:
                raise HTTPException(400, "acção só em modo assistindo")
            if assist_id:
                assist_store.log_action(conn, assist_id, user.username, f"action.{action}", {"domain": domain})
                conn.commit()
            return {"status": "ok", "action": action, "note": "passo marcado no audit log"}

        if action not in assist_catalog.DESK_ACTIONS:
            raise HTTPException(400, f"acção inválida: {action}")
        spec = assist_catalog.DESK_ACTIONS[action]
        if assist_catalog.funnel_rank(meta.get("funnel_stage")) < spec["min_rank"]:
            raise HTTPException(400, "etapa do funil insuficiente para esta acção")
        if action not in ("dns.revalidate", "account.retry_sync") and not is_assisting:
            raise HTTPException(400, "assuma a sessão antes desta acção")

        method, path, body = assist_catalog.build_vm112_request(action, domain, ticket_payload)
        vm112_result = _call_vm112(method, path, body, session_id=sid)
        if not assist_id:
            ticket_id = assist_store.ensure_onboard_ticket(conn, sid, domain)
            cur = conn.execute(
                """
                INSERT INTO assist_sessions
                (session_id, ticket_id, initiated_by, initiated_by_user, status, funnel_stage, domain, started_at)
                VALUES (?, ?, 'technician', ?, 'escalated', ?, ?, ?)
                """,
                (sid, ticket_id, user.username, meta.get("funnel_stage"), domain, now),
            )
            assist_id = int(cur.lastrowid)
        assist_store.log_action(
            conn,
            assist_id,
            user.username,
            f"action.{action}",
            {"domain": domain, "vm112": vm112_result},
        )
        conn.commit()

    if vm112_result is None and not VM112_ASSIST_CALL:
        return {
            "status": "logged",
            "action": action,
            "note": "VM112_ASSIST_CALL_VM112=false — acção registada no audit",
        }
    if vm112_result and vm112_result.get("http_status", 0) >= 400:
        raise HTTPException(502, detail={"message": "VM112 rejeitou acção", "vm112": vm112_result})
    return {"status": "ok", "action": action, "vm112": vm112_result}


@router.get("/technicians/ranking")
def technicians_ranking(
    window_days: int = 30,
    user: auth.DeskUser = Depends(auth.get_current_user),
):
    if not can_read_assist(user.role):
        raise HTTPException(403, "permissão insuficiente")
    if user.role == "noc":
        raise HTTPException(403, "permissão insuficiente")
    days = max(1, min(window_days, 365))
    m = _main()
    with m.db() as conn:
        ranking = assist_catalog.technician_ranking(conn, window_days=days)
    return {"window_days": days, "ranking": ranking, "total": len(ranking)}


def process_assist_started(conn, body, now: str) -> dict:
    m = _main()
    sid = (body.session_id or "").strip()
    technician = (body.data or {}).get("technician")
    meta = assist_store.session_funnel_meta(
        conn, sid, m.FUNNEL_EVENT_RANK, m.FUNNEL_STAGE_BY_RANK, m.ONBOARD_SOURCE
    )
    ticket_id = assist_store.ensure_onboard_ticket(conn, sid, body.domain or meta.get("domain"))
    conn.execute(
        """
        UPDATE tickets
        SET status = 'assisting', session_id = ?, assist_mode = 'asm',
            assisted_by = ?, assisted_at = ?, client_paused = 1
        WHERE id = ?
        """,
        (sid, technician, now, ticket_id),
    )
    return {"handled": True, "ticket_id": ticket_id, "session_id": sid}


def process_assist_ended(conn, body, now: str) -> dict:
    sid = (body.session_id or "").strip()
    ticket_row = assist_store.find_ticket_by_session(conn, sid)
    if ticket_row:
        conn.execute(
            "UPDATE tickets SET status = 'resolved', client_paused = 0, assist_mode = NULL WHERE id = ?",
            (int(ticket_row["id"]),),
        )
        return {"handled": True, "ticket_id": int(ticket_row["id"]), "session_id": sid}
    return {"handled": False, "session_id": sid}


def process_escalation_webhook(conn, body, now: str) -> dict:
    """Handle onboarding.escalated / onboarding.failed from VM112."""
    m = _main()
    sid = (body.session_id or "").strip()
    if not sid:
        return {"handled": False, "reason": "missing session_id"}
    meta = assist_store.session_funnel_meta(
        conn, sid, m.FUNNEL_EVENT_RANK, m.FUNNEL_STAGE_BY_RANK, m.ONBOARD_SOURCE
    )
    ticket_id = assist_store.ensure_onboard_ticket(conn, sid, body.domain or meta.get("domain"))
    conn.execute(
        "UPDATE tickets SET status = 'escalated', session_id = ? WHERE id = ?",
        (sid, ticket_id),
    )
    if not assist_store.get_open_assist(conn, sid):
        cur = conn.execute(
            """
            INSERT INTO assist_sessions
            (session_id, ticket_id, initiated_by, initiated_by_user, status, funnel_stage, domain, started_at)
            VALUES (?, ?, 'client', NULL, 'escalated', ?, ?, ?)
            """,
            (sid, ticket_id, meta.get("funnel_stage"), body.domain or meta.get("domain"), now),
        )
        assist_store.log_action(conn, int(cur.lastrowid), "client", "escalate", {"event": body.event})
    return {"handled": True, "ticket_id": ticket_id, "session_id": sid}
