import { useEffect, useMemo, useState } from "react";
import { Bot, Check, Copy, Sparkles, Wrench } from "lucide-react";
import { Sheet, SheetContent, SheetTitle } from "@/components/ui/sheet";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { LogEntry } from "../columns";
import { AutoRouterIcon, useIsAutoRoutedModelGroup } from "@/components/shared/table_cells";
import { AGENT_CALL_TYPES, MCP_CALL_TYPES } from "../constants";
import { getEventDisplayName } from "../utils";
import { ClassifyTag } from "./ClassifyTag";
import { DrawerHeader } from "./DrawerHeader";
import { SidebarToggle } from "./SidebarToggle";
import { useKeyboardNavigation } from "./useKeyboardNavigation";
import { LogDetailContent, GuardrailJumpLink } from "./LogDetailContent";
import { sessionSpendLogsCall } from "../../networking";
import { useQuery } from "@tanstack/react-query";
import { getSpendString } from "@/utils/dataUtils";
import { normalizeGuardrailEntries, sortSessionLogs, SessionLogSortMode } from "./utils";
import { DRAWER_WIDTH } from "./constants";
import { useLogDetails } from "@/app/(dashboard)/hooks/logDetails/useLogDetails";

export interface LogDetailsDrawerProps {
  open: boolean;
  onClose: () => void;
  logEntry: LogEntry | null;
  sessionId?: string | null;
  accessToken?: string | null;
  allLogs?: LogEntry[];
  onSelectLog?: (log: LogEntry) => void;
  startTime?: string;
}

const SIDEBAR_WIDTH_PX = 224;

// Session logs are fetched page-by-page from the paginated backend and
// accumulated so the drawer can show the whole session. page_size is the
// backend maximum (le=100); the page cap bounds the fetch and the
// (un-virtualized) sidebar list for pathological sessions, keeping the most
// recent logs since the endpoint returns newest-first.
const SESSION_PAGE_SIZE = 100;
const MAX_SESSION_PAGES = 50;

/* ------------------------------------------------------------------ */
/*  TraceEventRow — compact event row used in both session & non-     */
/*  session sidebar lists.  Extracted to avoid JSX duplication.       */
/* ------------------------------------------------------------------ */
interface TraceEventRowProps {
  row: LogEntry;
  isSelected: boolean;
  onClick: () => void;
}

const TRACE_EVENT_ICON_CLASS = "text-muted-foreground shrink-0";

function TraceEventIcon({ callType, isAutoRouted }: { callType: string; isAutoRouted: boolean }) {
  if (MCP_CALL_TYPES.includes(callType)) return <Wrench size={12} className={TRACE_EVENT_ICON_CLASS} />;
  if (AGENT_CALL_TYPES.includes(callType)) return <Bot size={12} className={TRACE_EVENT_ICON_CLASS} />;
  if (isAutoRouted) return <AutoRouterIcon size={12} className={TRACE_EVENT_ICON_CLASS} />;
  return <Sparkles size={12} className={TRACE_EVENT_ICON_CLASS} />;
}

function TraceEventRow({ row, isSelected, onClick }: TraceEventRowProps) {
  const isAutoRouted = useIsAutoRoutedModelGroup(row.model_group);
  const durationValue =
    row.request_duration_ms != null
      ? (row.request_duration_ms / 1000).toFixed(3)
      : row.startTime && row.endTime
        ? ((Date.parse(row.endTime) - Date.parse(row.startTime)) / 1000).toFixed(3)
        : "-";

  return (
    <button
      type="button"
      className={`w-full text-left pl-8 pr-2 py-1 transition-colors ${isSelected ? "bg-info/10" : "hover:bg-accent"}`}
      onClick={onClick}
    >
      <div className="flex items-center gap-1">
        <TraceEventIcon callType={row.call_type} isAutoRouted={isAutoRouted} />
        <span className="text-xs font-medium text-foreground truncate">
          {getEventDisplayName(row.call_type, row.model)}
        </span>
        <ClassifyTag origin={row.metadata?.internal_call_origin} className="ml-auto" />
      </div>
      <div className="text-[10px] text-muted-foreground mt-0 flex items-center gap-1.5 font-mono">
        <span>{durationValue}s</span>
        {row.spend ? (
          <>
            <span>·</span>
            <span>{getSpendString(row.spend)}</span>
          </>
        ) : null}
        {row.total_tokens ? (
          <>
            <span>·</span>
            <span>{row.total_tokens} tok</span>
          </>
        ) : null}
      </div>
    </button>
  );
}

/**
 * Right-side drawer panel for displaying detailed log information.
 * Features:
 * - Request ID prominently displayed with copy functionality
 * - Keyboard navigation (J/K for next/prev, Escape to close)
 * - Formatted and JSON view toggle for request/response
 * - Smart display of cache fields (hidden when zero)
 * - Error alerts for failed requests
 * - Collapsible sections for guardrails, vector store, metadata
 */
export function LogDetailsDrawer({
  open,
  onClose,
  logEntry,
  sessionId,
  accessToken,
  allLogs = [],
  onSelectLog,
  startTime,
}: LogDetailsDrawerProps) {
  const isSessionMode = Boolean(sessionId);
  const [selectedSessionRequestId, setSelectedSessionRequestId] = useState<string | null>(null);
  const [sessionSortMode, setSessionSortMode] = useState<SessionLogSortMode>("duration");
  const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
  const [copiedLeftPanelId, setCopiedLeftPanelId] = useState(false);

  const { data: sessionData } = useQuery({
    queryKey: ["sessionLogs", sessionId],
    queryFn: async () => {
      if (!sessionId || !accessToken) return { logs: [] as LogEntry[], total: 0 };

      // Fetch the first page, then page through the rest so sessions with more
      // than one page of logs are shown in full (capped for safety).
      const firstPage = await sessionSpendLogsCall(accessToken, sessionId, 1, SESSION_PAGE_SIZE);
      let rows: LogEntry[] = firstPage.data || firstPage || [];
      const pagesToFetch = Math.min(firstPage.total_pages ?? 1, MAX_SESSION_PAGES);

      if (pagesToFetch > 1) {
        const BATCH = 5;
        const remaining: Awaited<ReturnType<typeof sessionSpendLogsCall>>[] = [];
        for (let start = 2; start <= pagesToFetch; start += BATCH) {
          const end = Math.min(start + BATCH - 1, pagesToFetch);
          const batch = await Promise.all(
            Array.from({ length: end - start + 1 }, (_, i) =>
              sessionSpendLogsCall(accessToken, sessionId, start + i, SESSION_PAGE_SIZE),
            ),
          );
          remaining.push(...batch);
        }
        for (const page of remaining) {
          rows = rows.concat(page.data || []);
        }
      }

      // Fall back to the accumulated row count (not just the first page) when the
      // backend omits total, so the truncation note reflects what was fetched.
      const total: number = firstPage.total ?? rows.length;

      const logs = rows.map((row) => ({
        ...row,
        request_duration_ms: row.request_duration_ms ?? Date.parse(row.endTime) - Date.parse(row.startTime),
      }));

      return { logs, total };
    },
    enabled: Boolean(open && isSessionMode && sessionId && accessToken),
  });

  const sessionLogs: LogEntry[] = useMemo(
    () => sortSessionLogs(sessionData?.logs ?? [], sessionSortMode),
    [sessionData, sessionSortMode],
  );
  // total reported by the backend; when the page cap truncates the fetch this
  // exceeds sessionLogs.length, which drives the "showing most recent" note.
  const sessionTotalCount = sessionData?.total ?? sessionLogs.length;
  const sessionTruncated = sessionTotalCount > sessionLogs.length;

  // Default selection for a freshly opened session: the most recent log (latest
  // startTime). The list is ordered by the selected sort mode, so the latest
  // log by time is not necessarily sessionLogs[0]; compute it explicitly.
  // A clicked/remembered log still wins over this default.
  const mostRecentLog = useMemo<LogEntry | null>(
    () =>
      sessionLogs.reduce<LogEntry | null>(
        (latest, row) =>
          !latest || new Date(row.startTime).getTime() > new Date(latest.startTime).getTime() ? row : latest,
        null,
      ),
    [sessionLogs],
  );

  const currentLog = useMemo(() => {
    if (!isSessionMode) return logEntry;
    if (!sessionLogs.length) return null;
    const fallbackLog = mostRecentLog ?? sessionLogs[0];
    if (selectedSessionRequestId) {
      return sessionLogs.find((row) => row.request_id === selectedSessionRequestId) || fallbackLog;
    }
    if (logEntry?.request_id) {
      const clickedLog = sessionLogs.find((row) => row.request_id === logEntry.request_id);
      return clickedLog || fallbackLog;
    }
    return fallbackLog;
  }, [isSessionMode, logEntry, selectedSessionRequestId, sessionLogs, mostRecentLog]);

  useEffect(() => {
    if (!isSessionMode || !sessionLogs.length) return;
    if (!selectedSessionRequestId || !sessionLogs.some((row) => row.request_id === selectedSessionRequestId)) {
      const fallbackRequestId =
        logEntry?.request_id && sessionLogs.some((row) => row.request_id === logEntry.request_id)
          ? logEntry.request_id
          : (mostRecentLog ?? sessionLogs[0]).request_id;
      setSelectedSessionRequestId(fallbackRequestId);
    }
  }, [isSessionMode, logEntry, selectedSessionRequestId, sessionLogs, mostRecentLog]);

  // Reset transient UI state when the drawer opens or closes.
  useEffect(() => {
    if (open) {
      setIsSidebarCollapsed(false);
    } else {
      if (isSessionMode) setSelectedSessionRequestId(null);
      setSessionSortMode("duration");
      setCopiedLeftPanelId(false);
    }
  }, [open, isSessionMode]);

  // Keyboard navigation
  const { selectNextLog, selectPreviousLog } = useKeyboardNavigation({
    isOpen: open,
    currentLog,
    allLogs: isSessionMode ? sessionLogs : allLogs,
    onClose,
    onSelectLog: (selected) => {
      if (isSessionMode) {
        setSelectedSessionRequestId(selected.request_id);
      }
      onSelectLog?.(selected);
    },
  });

  // Lazy-load log details (messages/response) only when drawer is open.
  // This fetches data for a single log on-demand instead of prefetching all 50.
  const logDetails = useLogDetails(currentLog?.request_id, startTime, open && !!currentLog?.request_id);
  const detailsData = logDetails.data as any;
  const isLoadingDetails = logDetails.isLoading;

  // Build an enriched log entry that merges lazy-loaded details.
  // The list endpoint may already include messages/response when store_prompts_in_spend_logs is enabled,
  // while the detail endpoint fetches from custom loggers (S3, GCS, etc.) or DB fallback.
  const enrichedLog = useMemo(() => {
    if (!currentLog) return null;
    return {
      ...currentLog,
      messages: detailsData?.messages || currentLog.messages,
      response: detailsData?.response || currentLog.response,
      proxy_server_request: detailsData?.proxy_server_request || currentLog.proxy_server_request,
    };
  }, [currentLog, detailsData]);

  const metadata = currentLog?.metadata || {};

  // Status display values
  const statusLabel = metadata.status === "failure" ? "Failure" : "Success";
  const statusColor = metadata.status === "failure" ? ("error" as const) : ("success" as const);
  const environment = metadata?.user_api_key_team_alias || "default";

  const totalSessionCost = sessionLogs.reduce((sum, row) => sum + (row.spend || 0), 0);
  const sessionStart =
    sessionLogs.length > 0 ? new Date(Math.min(...sessionLogs.map((r) => new Date(r.startTime).getTime()))) : null;
  const sessionEnd =
    sessionLogs.length > 0 ? new Date(Math.max(...sessionLogs.map((r) => new Date(r.endTime).getTime()))) : null;
  const sessionDurationSeconds =
    sessionStart && sessionEnd ? ((sessionEnd.getTime() - sessionStart.getTime()) / 1000).toFixed(2) : "0.00";
  const llmCount = sessionLogs.filter(
    (row) => !MCP_CALL_TYPES.includes(row.call_type) && !AGENT_CALL_TYPES.includes(row.call_type),
  ).length;
  const agentCount = sessionLogs.filter((row) => AGENT_CALL_TYPES.includes(row.call_type)).length;
  const mcpCount = sessionLogs.filter((row) => MCP_CALL_TYPES.includes(row.call_type)).length;
  const cacheHitCount = sessionLogs.filter((row) => String(row.cache_hit ?? "").toLowerCase() === "true").length;
  const logsForList = isSessionMode ? sessionLogs : currentLog ? [currentLog] : [];
  const leftPanelId = isSessionMode ? sessionId || "" : currentLog?.request_id || "";
  const leftPanelDisplayId = leftPanelId.length > 14 ? `${leftPanelId.slice(0, 11)}...` : leftPanelId;

  const handleCopyLeftPanelId = async () => {
    if (!leftPanelId) return;
    try {
      await navigator.clipboard.writeText(leftPanelId);
      setCopiedLeftPanelId(true);
      setTimeout(() => setCopiedLeftPanelId(false), 1200);
    } catch {
      /* clipboard unavailable in non-secure contexts */
    }
  };

  if (!currentLog || !enrichedLog) return null;

  return (
    <Sheet
      open={open}
      onOpenChange={(nextOpen) => {
        if (!nextOpen) onClose();
      }}
    >
      <SheetContent
        side="right"
        showCloseButton={false}
        className="gap-0 overflow-hidden p-0 data-[side=right]:sm:max-w-none"
        style={{ width: DRAWER_WIDTH }}
      >
        <SheetTitle className="sr-only">
          {logEntry?.request_id ? `Request ${logEntry.request_id} details` : "Request details"}
        </SheetTitle>
        <div style={{ height: "100%" }} className="flex relative">
          {!isSidebarCollapsed && (
            <SidebarToggle
              isCollapsed={false}
              onToggle={() => setIsSidebarCollapsed(true)}
              className="absolute top-2 left-2 z-raised"
            />
          )}
          {!isSidebarCollapsed && (
            <div className="border-r border-border bg-muted flex flex-col" style={{ width: SIDEBAR_WIDTH_PX }}>
              <div className="pl-12 pr-3 py-2 border-b border-border bg-card">
                <div className="flex items-start justify-between gap-2">
                  <div>
                    <div className="text-[10px] uppercase tracking-wide text-muted-foreground">
                      {isSessionMode ? "Session" : "Trace"}
                    </div>
                    <div className="font-mono text-[12px] text-foreground leading-tight flex items-center gap-1">
                      <span className="truncate">{leftPanelDisplayId}</span>
                      <button
                        type="button"
                        onClick={handleCopyLeftPanelId}
                        className="text-muted-foreground hover:text-foreground"
                        aria-label="Copy trace id"
                      >
                        {copiedLeftPanelId ? <Check className="size-3" /> : <Copy className="size-3" />}
                      </button>
                    </div>
                  </div>
                </div>
                <div className="mt-1 text-[11px] text-muted-foreground font-mono">
                  {logsForList.length} req
                  {[
                    isSessionMode
                      ? llmCount
                      : logsForList.filter(
                          (row) => !MCP_CALL_TYPES.includes(row.call_type) && !AGENT_CALL_TYPES.includes(row.call_type),
                        ).length,
                    isSessionMode
                      ? agentCount
                      : logsForList.filter((row) => AGENT_CALL_TYPES.includes(row.call_type)).length,
                    isSessionMode
                      ? mcpCount
                      : logsForList.filter((row) => MCP_CALL_TYPES.includes(row.call_type)).length,
                  ].map((count, i) => {
                    const label = [" LLM", " Agent", " MCP"][i];
                    return count > 0 ? (
                      <span key={label}>
                        <span className="mx-1.5">·</span>
                        {count}
                        {label}
                      </span>
                    ) : null;
                  })}
                  <span className="mx-1.5">·</span>
                  {isSessionMode ? getSpendString(totalSessionCost) : getSpendString(currentLog.spend || 0)}
                  {isSessionMode && (
                    <>
                      <span className="mx-1.5">·</span>
                      {sessionDurationSeconds}s
                    </>
                  )}
                </div>
                {isSessionMode && (
                  <div className="text-[11px] text-muted-foreground font-mono whitespace-nowrap">
                    {cacheHitCount}/{logsForList.length} cached
                  </div>
                )}
                {isSessionMode && sessionTruncated && (
                  <div className="mt-1 text-[11px] text-warning font-mono">
                    Showing most recent {logsForList.length} of {sessionTotalCount}
                  </div>
                )}
                {isSessionMode && (
                  <Tabs
                    className="mt-1.5"
                    value={sessionSortMode}
                    onValueChange={(value) => setSessionSortMode(value as SessionLogSortMode)}
                  >
                    <TabsList className="w-full">
                      <TabsTrigger value="duration" className="text-[11px]">
                        Duration
                      </TabsTrigger>
                      <TabsTrigger value="start_time" className="text-[11px]">
                        Start time
                      </TabsTrigger>
                    </TabsList>
                  </Tabs>
                )}
              </div>

              <div className="flex-1 overflow-y-auto">
                {normalizeGuardrailEntries(metadata?.guardrail_information).length > 0 && (
                  <div className="px-3 pt-2">
                    <GuardrailJumpLink guardrailEntries={normalizeGuardrailEntries(metadata?.guardrail_information)} />
                  </div>
                )}
                {isSessionMode ? (
                  <div className="py-1">
                    {/* Child events — vertical tree line with horizontal connectors */}
                    <div className="relative pl-2">
                      <div className="absolute left-4 top-1 bottom-1 border-l border-border" />
                      {logsForList.map((row, idx) => {
                        const isLast = idx === logsForList.length - 1;
                        return (
                          <div key={row.request_id} className="relative">
                            <div className="absolute left-4 top-3 w-3 border-t border-border" />
                            {isLast && <div className="absolute left-4 top-3 bottom-0 w-px bg-muted" />}
                            <TraceEventRow
                              row={row}
                              isSelected={row.request_id === currentLog.request_id}
                              onClick={() => {
                                setSelectedSessionRequestId(row.request_id);
                                onSelectLog?.(row);
                              }}
                            />
                          </div>
                        );
                      })}
                    </div>
                  </div>
                ) : (
                  <div className="py-1">
                    {logsForList.map((row) => (
                      <TraceEventRow
                        key={row.request_id}
                        row={row}
                        isSelected={row.request_id === currentLog.request_id}
                        onClick={() => onSelectLog?.(row)}
                      />
                    ))}
                  </div>
                )}
              </div>
            </div>
          )}

          <div className="flex-1 flex flex-col overflow-hidden">
            <DrawerHeader
              log={currentLog}
              onClose={onClose}
              isSidebarCollapsed={isSidebarCollapsed}
              onToggleSidebar={() => setIsSidebarCollapsed((collapsed) => !collapsed)}
              onPrevious={selectPreviousLog}
              onNext={selectNextLog}
              statusLabel={statusLabel}
              statusColor={statusColor}
              environment={environment}
            />
            <div className="flex-1 overflow-y-auto">
              <LogDetailContent
                logEntry={enrichedLog}
                isLoadingDetails={isLoadingDetails}
                accessToken={accessToken ?? null}
              />
            </div>
          </div>
        </div>
      </SheetContent>
    </Sheet>
  );
}
