import { Check, Copy, GitCompareArrows, RotateCcw } from "lucide-react";
import type { JSX } from "react";
import { useCallback, useEffect, useMemo, useRef, useState, memo } from "react";
import { cn } from "../../lib/utils";
import type { CapturedLog } from "../../proxy/schemas";
import { stripClaudeCodeBillingHeader } from "../../proxy/claudeCodeStrip";
import { Button } from "../ui/button";
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
import { JsonViewerFromString } from "../ui/json-viewer";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
import { computeHeadersDiff, computeRequestDiff, DiffView } from "./diff";
import { LogEntryHeader } from "./LogEntryHeader";
import { ReplayDialog } from "./ReplayDialog";
import { ResponseView } from "./ResponseView";
import { StreamingChunkSequence } from "./StreamingChunkSequence";
import type { CacheTrendEntry } from "./cacheTrend";
import { getLogFormatAdapter, resolveLogFormat } from "./log-formats";

export type LogEntryProps = {
  log: CapturedLog;
  viewMode?: "simple" | "full";
  /** Live "strip Claude Code billing header" flag, sourced once at the viewer
   * container. Hoisted out of `LogEntry` so a single SWR subscription serves
   * the whole virtualized list (N logs == N subscriptions is the previous
   * cost).
   */
  strip: boolean;
  /**
   * Per-log cache token trend, looked up in the viewer-level trend map.
   * `null` (or absent) means the header should render with no arrows.
   */
  cacheTrend?: CacheTrendEntry | null;
  /** Callback to open CompareDrawer with this log and its immediate predecessor. */
  onCompareWithPrevious?: (log: CapturedLog) => void;
};

/**
 * Pure visibility rule for the "Raw Request" tab. Extracted so it can be
 * unit-tested without rendering React. The tab appears only when all three
 * are true: anthropic format, full view mode, strip toggle enabled.
 */
export function shouldShowRawRequestTab(
  apiFormat: string,
  viewMode: "simple" | "full",
  strip: boolean,
): boolean {
  return apiFormat === "anthropic" && viewMode === "full" && strip;
}

/**
 * Pure visibility rule for the "Diff with Raw" button in the Headers tab.
 * The button only makes sense when the user is in full mode (where the
 * `Raw Headers` tab is shown) and we actually captured raw headers.
 */
export function shouldShowHeadersDiffButton(
  viewMode: "simple" | "full",
  hasRawHeaders: boolean,
): boolean {
  return viewMode === "full" && hasRawHeaders;
}

/**
 * Pure visibility rule for the "Diff with Raw" button in the Request tab.
 * Mirrors the conditions for the `Raw Request` tab itself: full mode plus
 * the strip toggle being on for an anthropic-format request. We also need
 * an actual raw request body to diff against.
 */
export function shouldShowRequestDiffButton(
  apiFormat: string,
  viewMode: "simple" | "full",
  strip: boolean,
  hasRawRequest: boolean,
): boolean {
  return apiFormat === "anthropic" && viewMode === "full" && strip && hasRawRequest;
}

function CopyButton({
  text,
  label,
  copied,
  onCopy,
}: {
  text: string | null;
  label: string;
  copied: boolean;
  onCopy: (e: React.MouseEvent) => void;
}): JSX.Element | null {
  if (text === null) return null;
  return (
    <button
      type="button"
      onClick={onCopy}
      className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors px-2 py-1 rounded hover:bg-muted"
    >
      {copied ? (
        <>
          <Check className="size-3 text-green-500" />
          <span className="text-green-500">Copied!</span>
        </>
      ) : (
        <>
          <Copy className="size-3" />
          <span>{label}</span>
        </>
      )}
    </button>
  );
}

function DiffToggleButton({
  active,
  onClick,
}: {
  active: boolean;
  onClick: (e: React.MouseEvent) => void;
}): JSX.Element {
  return (
    <Tooltip>
      <TooltipTrigger asChild>
        <button
          type="button"
          onClick={onClick}
          aria-pressed={active}
          className={cn(
            "flex items-center gap-1.5 text-xs px-2 py-1 rounded transition-colors",
            active
              ? "bg-primary/10 text-primary"
              : "text-muted-foreground hover:text-foreground hover:bg-muted",
          )}
        >
          <GitCompareArrows className="size-3" />
          {active ? "Showing diff" : "Diff with Raw"}
        </button>
      </TooltipTrigger>
      <TooltipContent>
        {active ? "Hide diff view" : "Compare proxy output against the original raw version"}
      </TooltipContent>
    </Tooltip>
  );
}

const RequestDiffContent = memo(function ({
  rawBody,
  displayedBody,
  emptyLabel,
}: {
  rawBody: string | null;
  displayedBody: string | null;
  emptyLabel: string;
}): JSX.Element {
  const result = useMemo(
    () => computeRequestDiff(rawBody, displayedBody),
    [rawBody, displayedBody],
  );
  return <DiffView result={result} emptyLabel={emptyLabel} />;
});

const HeadersDiffContent = memo(function ({
  rawHeaders,
  headers,
  emptyLabel,
}: {
  rawHeaders: Record<string, string> | undefined;
  headers: Record<string, string> | undefined;
  emptyLabel: string;
}): JSX.Element {
  const result = useMemo(() => computeHeadersDiff(rawHeaders, headers), [rawHeaders, headers]);
  return <DiffView result={result} emptyLabel={emptyLabel} />;
});

function useCopyFeedback(text: string | null): {
  copied: boolean;
  copy: (event: React.MouseEvent) => void;
} {
  const [copied, setCopied] = useState(false);
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(
    () => () => {
      if (timerRef.current !== null) clearTimeout(timerRef.current);
    },
    [],
  );

  const copy = useCallback(
    (event: React.MouseEvent) => {
      event.stopPropagation();
      if (text === null) return;
      void window.navigator.clipboard.writeText(text).then(() => {
        setCopied(true);
        if (timerRef.current !== null) clearTimeout(timerRef.current);
        timerRef.current = setTimeout(() => setCopied(false), 2000);
      });
    },
    [text],
  );

  return { copied, copy };
}

export const LogEntry = memo(function ({
  log,
  viewMode = "simple",
  strip,
  cacheTrend = null,
  onCompareWithPrevious,
}: LogEntryProps): JSX.Element {
  const [expanded, setExpanded] = useState<boolean>(false);
  const [replayOpen, setReplayOpen] = useState<boolean>(false);
  const [headersDiff, setHeadersDiff] = useState<boolean>(false);
  const [requestDiff, setRequestDiff] = useState<boolean>(false);
  const [activeTab, setActiveTab] = useState("request");
  const resolvedFormat = resolveLogFormat(log);
  const adapter = getLogFormatAdapter(resolvedFormat);
  const requestAnalysis = useMemo(
    () => adapter.analyzeRequest(log.rawRequestBody),
    [adapter, log.rawRequestBody],
  );
  const responseAnalysis = useMemo(
    () => adapter.analyzeResponse(log.responseText),
    [adapter, log.responseText],
  );
  const strippedRequestBody = useMemo(() => {
    if (!strip || resolvedFormat !== "anthropic" || log.rawRequestBody === null) {
      return null;
    }
    return stripClaudeCodeBillingHeader(log.rawRequestBody).body;
  }, [log.rawRequestBody, resolvedFormat, strip]);
  const displayedRequestBody = strippedRequestBody ?? log.rawRequestBody;
  const requestCopy = useCopyFeedback(displayedRequestBody);
  const rawRequestCopy = useCopyFeedback(log.rawRequestBody);
  const responseCopy = useCopyFeedback(log.responseText);

  return (
    <TooltipProvider>
      <div className="border border-border rounded-lg mb-1 overflow-hidden">
        <LogEntryHeader
          log={log}
          messageCount={requestAnalysis.messageCount}
          toolCount={requestAnalysis.toolCount}
          expanded={expanded}
          onToggle={() => setExpanded(!expanded)}
          responseToolNames={responseAnalysis.toolNames}
          cacheTrend={cacheTrend}
        />

        {expanded && (
          <div onClick={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()}>
            <Tabs value={activeTab} onValueChange={setActiveTab}>
              <TabsList className="mx-4 mt-2">
                {viewMode === "full" && <TabsTrigger value="raw-headers">Raw Headers</TabsTrigger>}
                {viewMode === "full" && <TabsTrigger value="headers">Headers</TabsTrigger>}
                {shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && (
                  <TabsTrigger value="raw-request">Raw Request</TabsTrigger>
                )}
                <TabsTrigger value="request">Request</TabsTrigger>
                {viewMode === "full" && <TabsTrigger value="raw">Raw Response</TabsTrigger>}
                <TabsTrigger value="parsed">Response</TabsTrigger>
              </TabsList>

              {shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && (
                <TabsContent value="raw-request">
                  {activeTab === "raw-request" && (
                    <div className="px-4 py-3">
                      <div className="flex justify-end mb-2">
                        <CopyButton
                          text={log.rawRequestBody}
                          label="Copy Raw Request"
                          copied={rawRequestCopy.copied}
                          onCopy={rawRequestCopy.copy}
                        />
                      </div>
                      {log.rawRequestBody !== null ? (
                        <JsonViewerFromString text={log.rawRequestBody} defaultExpandDepth={1} />
                      ) : (
                        <p className="text-xs text-muted-foreground italic">No request body</p>
                      )}
                    </div>
                  )}
                </TabsContent>
              )}

              <TabsContent value="request">
                {activeTab === "request" && (
                  <div className="px-4 py-3">
                    <div className="flex justify-end gap-2 mb-2">
                      {shouldShowRequestDiffButton(
                        resolvedFormat,
                        viewMode,
                        strip,
                        log.rawRequestBody !== null,
                      ) && (
                        <DiffToggleButton
                          active={requestDiff}
                          onClick={(e) => {
                            e.stopPropagation();
                            setRequestDiff(!requestDiff);
                          }}
                        />
                      )}
                      {onCompareWithPrevious !== undefined && (
                        <Tooltip>
                          <TooltipTrigger asChild>
                            <Button
                              variant="outline"
                              size="sm"
                              className="h-7 text-xs"
                              onClick={(e) => {
                                e.stopPropagation();
                                onCompareWithPrevious(log);
                              }}
                            >
                              <GitCompareArrows className="size-3 mr-1" />
                              Diff with Previous
                            </Button>
                          </TooltipTrigger>
                          <TooltipContent>
                            Compare this request with the immediately preceding one
                          </TooltipContent>
                        </Tooltip>
                      )}
                      <Tooltip>
                        <TooltipTrigger asChild>
                          <Button
                            variant="outline"
                            size="sm"
                            className="h-7 text-xs"
                            onClick={(e) => {
                              e.stopPropagation();
                              setReplayOpen(true);
                            }}
                          >
                            <RotateCcw className="size-3 mr-1" />
                            Replay
                          </Button>
                        </TooltipTrigger>
                        <TooltipContent>Re-send this request to the provider</TooltipContent>
                      </Tooltip>
                      <CopyButton
                        text={displayedRequestBody}
                        label="Copy Request"
                        copied={requestCopy.copied}
                        onCopy={requestCopy.copy}
                      />
                    </div>
                    {requestDiff ? (
                      <RequestDiffContent
                        rawBody={log.rawRequestBody}
                        displayedBody={displayedRequestBody}
                        emptyLabel="No transformation applied — raw and sent request bodies are identical."
                      />
                    ) : displayedRequestBody !== null ? (
                      <JsonViewerFromString text={displayedRequestBody} defaultExpandDepth={1} />
                    ) : (
                      <p className="text-xs text-muted-foreground italic">No request body</p>
                    )}
                  </div>
                )}
              </TabsContent>

              {viewMode === "full" && (
                <TabsContent value="headers">
                  {activeTab === "headers" && (
                    <div className="px-4 py-3">
                      <div className="flex justify-end gap-2 mb-2">
                        {shouldShowHeadersDiffButton(
                          viewMode,
                          log.rawHeaders !== undefined && Object.keys(log.rawHeaders).length > 0,
                        ) && (
                          <DiffToggleButton
                            active={headersDiff}
                            onClick={(e) => {
                              e.stopPropagation();
                              setHeadersDiff(!headersDiff);
                            }}
                          />
                        )}
                      </div>
                      {headersDiff ? (
                        <HeadersDiffContent
                          rawHeaders={log.rawHeaders}
                          headers={log.headers}
                          emptyLabel="No transformation applied — raw and processed headers are identical."
                        />
                      ) : log.headers && Object.keys(log.headers).length > 0 ? (
                        <div className="space-y-1 font-mono text-xs">
                          {Object.entries(log.headers)
                            .sort(([a], [b]) => a.localeCompare(b))
                            .map(([key, value]) => (
                              <div key={key} className="flex gap-2">
                                <span className="text-blue-600 dark:text-blue-400 font-semibold shrink-0">
                                  {key}:
                                </span>
                                <span className="text-muted-foreground truncate" title={value}>
                                  {value}
                                </span>
                              </div>
                            ))}
                        </div>
                      ) : (
                        <p className="text-xs text-muted-foreground italic">No headers captured</p>
                      )}
                    </div>
                  )}
                </TabsContent>
              )}

              {viewMode === "full" && (
                <TabsContent value="raw-headers">
                  {activeTab === "raw-headers" && (
                    <div className="px-4 py-3">
                      {log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? (
                        <div className="space-y-1 font-mono text-xs">
                          {Object.entries(log.rawHeaders)
                            .sort(([a], [b]) => a.localeCompare(b))
                            .map(([key, value]) => (
                              <div key={key} className="flex gap-2">
                                <span className="text-blue-600 dark:text-blue-400 font-semibold shrink-0">
                                  {key}:
                                </span>
                                <span className="text-muted-foreground truncate" title={value}>
                                  {value}
                                </span>
                              </div>
                            ))}
                        </div>
                      ) : (
                        <p className="text-xs text-muted-foreground italic">
                          No raw headers captured
                        </p>
                      )}
                    </div>
                  )}
                </TabsContent>
              )}

              <TabsContent value="raw">
                {activeTab === "raw" && (
                  <div className="px-4 py-3 space-y-3">
                    {log.error !== undefined && log.error !== null && (
                      <div className="rounded border border-destructive/50 bg-destructive/10 p-3 text-xs">
                        <div className="font-semibold text-destructive mb-1">SSE Error</div>
                        <div className="text-muted-foreground font-mono">{log.error}</div>
                      </div>
                    )}
                    <div className="flex justify-end">
                      <CopyButton
                        text={log.responseText}
                        label="Copy Response"
                        copied={responseCopy.copied}
                        onCopy={responseCopy.copy}
                      />
                    </div>
                    {log.responseText !== null ? (
                      <JsonViewerFromString text={log.responseText} defaultExpandDepth={1} />
                    ) : (
                      <p className="text-xs text-muted-foreground italic">No response</p>
                    )}
                    {log.streaming === true && (
                      <StreamingChunkSequence
                        logId={log.id}
                        truncated={log.streamingChunksPath !== null}
                      />
                    )}
                  </div>
                )}
              </TabsContent>

              <TabsContent value="parsed">
                {activeTab === "parsed" && (
                  <div className="px-4 py-3">
                    <ResponseView
                      responseText={log.responseText}
                      responseStatus={log.responseStatus}
                      streaming={log.streaming}
                      inputTokens={log.inputTokens}
                      outputTokens={log.outputTokens}
                      cacheCreationInputTokens={log.cacheCreationInputTokens}
                      cacheReadInputTokens={log.cacheReadInputTokens}
                      apiFormat={resolvedFormat}
                      error={log.error}
                    />
                  </div>
                )}
              </TabsContent>
            </Tabs>
          </div>
        )}
      </div>
      <ReplayDialog log={log} open={replayOpen} onOpenChange={setReplayOpen} />
    </TooltipProvider>
  );
});
