import {
  gitCommitGet,
  gitCommitShortId,
  gitDiffTreeToTree,
  gitCommitParent,
} from "@/client";
import { useQuery } from "@tanstack/react-query";
import {
  AlertCircle,
  ArrowLeft,
  Calendar,
  Check,
  ChevronRight,
  Copy,
  File,
  FileCode,
  FilePlus,
  FileX,
  Loader2,
  Search,
} from "lucide-react";
import { useCallback, useMemo, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { toast } from "sonner";

// ─── Types ────────────────────────────────────────────────────────────────────

type DiffLine = {
  content: string;
  origin: string;
  old_lineno?: number;
  new_lineno?: number;
};

type DiffDelta = {
  status: string;
  old_file: { path?: string; oid?: string; size?: number; is_binary?: boolean };
  new_file: { path?: string; oid?: string; size?: number; is_binary?: boolean };
  nfiles?: number;
  lines: DiffLine[];
};

type DiffResult = {
  stats: { files_changed: number; insertions: number; deletions: number };
  deltas: DiffDelta[];
};

// ─── Helpers ──────────────────────────────────────────────────────────────────

const getRelativeTime = (timestamp: number | null | undefined) => {
  if (!timestamp) return "";
  const date = new Date(timestamp * 1000);
  const now = new Date();
  const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
  if (diffInSeconds < 60) return "just now";
  if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} minutes ago`;
  if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`;
  if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 86400)} days ago`;
  if (diffInSeconds < 31536000) return `${Math.floor(diffInSeconds / 2592000)} months ago`;
  return `${Math.floor(diffInSeconds / 31536000)} years ago`;
};

const getCommitMessageLines = (message: string | null | undefined) => {
  if (!message) return { title: "No message", description: "" };
  const lines = message.split("\n");
  return {
    title: lines[0] || "No message",
    description: lines.slice(1).join("\n").trim(),
  };
};

// ─── Sub-components ───────────────────────────────────────────────────────────

const FileListItem = ({
  file,
  isSelected,
  onClick,
}: {
  file: DiffDelta;
  isSelected: boolean;
  onClick: () => void;
}) => {
  const additions = file.lines.filter((l) => l.origin === "+").length;
  const deletions = file.lines.filter((l) => l.origin === "-").length;
  const path = file.new_file.path || file.old_file.path || "Unknown";
  const isRename = file.status === "renamed";
  const isCopy = file.status === "copied";

  return (
    <button
      onClick={onClick}
      className={`w-full text-left px-3 py-2.5 flex items-start gap-2 hover:bg-muted/50 transition-colors border-b border-border/30 ${
        isSelected ? "bg-muted border-l-2 border-l-primary" : ""
      }`}
    >
      <div className="mt-0.5 flex-shrink-0">
        {isRename || isCopy ? (
          <FileCode className="h-4 w-4 text-blue-500" />
        ) : (
          <File className="h-4 w-4 text-muted-foreground" />
        )}
      </div>
      <div className="flex-1 min-w-0">
        <div className="font-mono text-sm truncate text-foreground">{path}</div>
        <div className="flex items-center gap-2 mt-0.5 text-xs">
          {additions > 0 && (
            <span className="text-green-600 font-mono">+{additions}</span>
          )}
          {deletions > 0 && (
            <span className="text-red-600 font-mono">-{deletions}</span>
          )}
        </div>
      </div>
      {isSelected && (
        <ChevronRight className="h-4 w-4 text-muted-foreground flex-shrink-0 mt-0.5" />
      )}
    </button>
  );
};

const DiffViewer = ({ file }: { file: DiffDelta }) => {
  const path = file.new_file.path || file.old_file.path || "Unknown";
  const additions = file.lines.filter((l) => l.origin === "+").length;
  const deletions = file.lines.filter((l) => l.origin === "-").length;

  return (
    <div className="flex-1 overflow-y-auto">
      {/* File header */}
      <div className="sticky top-0 z-10 bg-muted/80 backdrop-blur border-b border-border px-4 py-2 flex items-center gap-2">
        <FileCode className="h-4 w-4 text-muted-foreground" />
        <span className="font-mono text-sm text-foreground">{path}</span>
        <div className="ml-auto flex items-center gap-3 text-xs font-mono">
          {additions > 0 && (
            <span className="text-green-600">+{additions}</span>
          )}
          {deletions > 0 && (
            <span className="text-red-600">-{deletions}</span>
          )}
        </div>
      </div>

      {/* Diff lines */}
      {file.lines.length > 0 ? (
        <div className="divide-y divide-border/50">
          {file.lines.map((line: DiffLine, idx: number) => {
            const isAddition = line.origin === "+";
            const isDeletion = line.origin === "-";
            const isHunk = line.origin === "h";
            const isContext = line.origin === " " || line.origin === "=";

            return (
              <div
                key={`${line.origin}-${line.old_lineno ?? 0}-${line.new_lineno ?? 0}-${idx}`}
                className={`flex font-mono text-sm ${
                  isAddition
                    ? "bg-green-500/10"
                    : isDeletion
                      ? "bg-red-500/10"
                      : isHunk
                        ? "bg-blue-500/5"
                        : ""
                }`}
              >
                {/* Old line number */}
                <div className="w-12 flex-shrink-0 px-2 py-0.5 text-right text-muted-foreground select-none border-r bg-muted/30 text-xs">
                  {isDeletion || isHunk || isContext
                    ? line.old_lineno ?? ""
                    : ""}
                </div>
                {/* New line number */}
                <div className="w-12 flex-shrink-0 px-2 py-0.5 text-right text-muted-foreground select-none border-r bg-muted/30 text-xs">
                  {isAddition || isHunk || isContext
                    ? line.new_lineno ?? ""
                    : ""}
                </div>
                {/* Origin marker */}
                <div className="w-6 flex-shrink-0 px-1 py-0.5 text-center select-none text-xs">
                  <span
                    className={
                      isAddition
                        ? "text-green-600"
                        : isDeletion
                          ? "text-red-600"
                          : isHunk
                            ? "text-blue-600"
                            : "text-muted-foreground"
                    }
                  >
                    {line.origin}
                  </span>
                </div>
                {/* Content */}
                <div className="flex-1 px-3 py-0.5 whitespace-pre-wrap break-all text-foreground">
                  {line.content}
                </div>
              </div>
            );
          })}
        </div>
      ) : (
        <div className="p-8 text-center text-muted-foreground">
          No changes to display
        </div>
      )}
    </div>
  );
};

// ─── Main component ────────────────────────────────────────────────────────────

export const RepoCommitDetail = () => {
  const { namespace, repoName, oid } = useParams<{
    namespace: string;
    repoName: string;
    oid: string;
  }>();
  const commitId = oid;
  const navigate = useNavigate();
  const [selectedFileIdx, setSelectedFileIdx] = useState(0);
  const [searchTerm, setSearchTerm] = useState("");
  const [copied, setCopied] = useState(false);

  // Fetch commit metadata
  const {
    data: commit,
    isLoading: commitLoading,
    error: commitError,
  } = useQuery({
    queryKey: ["commit", namespace, repoName, commitId],
    queryFn: async () => {
      if (!namespace || !repoName || !commitId) return null;
      const resp = await gitCommitGet({
        path: { namespace, repo: repoName, oid: commitId },
      });
      return resp.data?.data ?? null;
    },
    enabled: !!namespace && !!repoName && !!commitId,
    staleTime: 60 * 1000,
  });

  // Fetch parent commit (for first parent at index 0)
  const {
    data: parentCommit,
    isLoading: parentLoading,
  } = useQuery({
    queryKey: ["commit-parent", namespace, repoName, commitId],
    queryFn: async () => {
      if (!namespace || !repoName || !commitId) return null;
      // Index 0 = first parent
      const resp = await gitCommitParent({
        path: { namespace, repo: repoName, oid: commitId, index: 0 },
      });
      return resp.data?.data ?? null;
    },
    // Only run if commit has at least one parent
    enabled: !!namespace && !!repoName && !!commitId && !!commit?.parent_ids?.length,
    staleTime: 60 * 1000,
  });

  // Fetch diff: commit tree vs parent tree (or just commit tree if initial commit)
  const {
    data: diffData,
    isLoading: diffLoading,
    isFetching: diffFetching,
    error: diffError,
  } = useQuery({
    queryKey: ["commit-tree-diff", namespace, repoName, commitId],
    queryFn: async () => {
      if (!namespace || !repoName || !commitId) return null;
      const commitTreeId = commit?.tree_id;
      const parentTreeId = parentCommit?.tree_id;

      const resp = await gitDiffTreeToTree({
        path: { namespace, repo: repoName },
        query: {
          old_tree: parentTreeId ?? "",
          new_tree: commitTreeId ?? "",
        },
      });
      return resp.data?.data ?? null;
    },
    // Wait for commit to load, and if it has parents, wait for parent to load too
    enabled: !!namespace && !!repoName && !!commitId && !!commit?.tree_id &&
             (commit?.parent_ids?.length === 0 || !!parentCommit?.tree_id),
    staleTime: 30 * 1000,
  });

  // Short ID
  const { data: shortIdData } = useQuery({
    queryKey: ["commit-short-id", namespace, repoName, commitId],
    queryFn: async () => {
      if (!namespace || !repoName || !commitId) return null;
      const resp = await gitCommitShortId({
        path: { namespace, repo: repoName, oid: commitId },
      });
      return resp.data?.data ?? null;
    },
    enabled: !!namespace && !!repoName && !!commitId,
    staleTime: 60 * 1000,
  });

  const handleCopySha = async () => {
    if (!commitId) return;
    try {
      await navigator.clipboard.writeText(commitId);
      toast.success("SHA copied to clipboard");
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch {
      toast.error("Failed to copy");
    }
  };

  const shortId =
    shortIdData?.short_id || commitId?.substring(0, 7) || "";

  const diffResult = diffData as DiffResult | null;
  const deltas: DiffDelta[] = diffResult?.deltas ?? [];
  const stats = diffResult?.stats;

  const filteredFiles = useMemo(() => {
    if (!searchTerm) return deltas;
    const term = searchTerm.toLowerCase();
    return deltas.filter(
      (f: DiffDelta) =>
        f.new_file.path?.toLowerCase().includes(term) ||
        f.old_file.path?.toLowerCase().includes(term)
    );
  }, [deltas, searchTerm]);

  const currentFile = filteredFiles[selectedFileIdx] ?? null;
  const safeSelectedIdx = Math.min(
    selectedFileIdx,
    Math.max(0, filteredFiles.length - 1)
  );

  const handleSearchChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      setSearchTerm(e.target.value);
      setSelectedFileIdx(0);
    },
    []
  );

  const { title: commitTitle, description: commitDescription } =
    getCommitMessageLines(commit?.message);

  const isLoading = commitLoading || diffLoading || parentLoading;
  const hasError = commitError || diffError;

  // Reset selected file when commit changes
  const prevCommitId = commitId;
  if (prevCommitId !== commitId) {
    setSelectedFileIdx(0);
  }

  if (!namespace || !repoName) {
    return (
      <div className="flex items-center justify-center h-64">
        <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
        <span className="ml-2 text-muted-foreground">
          Loading repository...
        </span>
      </div>
    );
  }

  if (!commitId) {
    return (
      <div className="p-6 flex items-center justify-center h-64 text-destructive">
        <AlertCircle className="h-8 w-8" />
        <span className="ml-2">Commit SHA not provided</span>
      </div>
    );
  }

  if (isLoading) {
    return (
      <div className="flex items-center justify-center h-64">
        <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
        <span className="ml-2 text-muted-foreground">
          Loading commit diff...
        </span>
      </div>
    );
  }

  if (hasError || !commit) {
    return (
      <div className="p-6">
        <div className="rounded-md border border-red-300 bg-red-50 dark:bg-red-950/30 p-4">
          <h2 className="text-lg font-semibold text-red-600 dark:text-red-400 mb-2">
            Error loading commit
          </h2>
          <p className="text-sm text-red-500">
            {(commitError || diffError || {}).message || "Commit not found"}
          </p>
        </div>
      </div>
    );
  }

  const totalAdditions = stats?.insertions ?? 0;
  const totalDeletions = stats?.deletions ?? 0;
  const isInitialCommit = !commit.parent_ids?.length;

  return (
    <div className="h-[calc(100vh-4rem)] flex flex-col">
      {/* Header */}
      <div className="flex-shrink-0 border-b bg-card/80 backdrop-blur">
        <div className="px-6 py-4">
          <div className="flex items-start gap-4">
            <button
              onClick={() => navigate(-1)}
              className="mt-1 p-2 hover:bg-muted rounded-md transition-colors"
            >
              <ArrowLeft className="h-5 w-5" />
            </button>

            {/* Avatar */}
            <div className="h-10 w-10 mt-0.5 rounded-full bg-muted flex items-center justify-center flex-shrink-0">
              <span className="text-sm font-semibold text-muted-foreground">
                {(commit.author?.name || "U").charAt(0).toUpperCase()}
              </span>
            </div>

            <div className="flex-1 min-w-0">
              <h1 className="text-xl font-semibold leading-tight text-foreground">
                {commitTitle}
              </h1>
              {commitDescription && (
                <p className="text-sm text-muted-foreground mt-1">
                  {commitDescription}
                </p>
              )}

              <div className="flex flex-wrap items-center gap-x-4 gap-y-2 mt-3 text-sm">
                <span className="font-medium text-foreground">
                  {commit.author?.name || "Unknown"}
                </span>
                <span className="flex items-center gap-1 text-muted-foreground">
                  <Calendar className="h-3.5 w-3.5" />
                  {getRelativeTime(commit.committer?.time_secs)}
                </span>
                <div className="flex items-center gap-2">
                  <code
                    className="px-2 py-0.5 rounded bg-muted font-mono text-xs border border-border cursor-pointer hover:bg-muted/70 transition-colors"
                    onClick={handleCopySha}
                  >
                    {shortId}
                  </code>
                  <button
                    onClick={handleCopySha}
                    className="p-1 hover:bg-muted rounded transition-colors"
                  >
                    {copied ? (
                      <Check className="h-3.5 w-3.5 text-green-600" />
                    ) : (
                      <Copy className="h-3.5 w-3.5" />
                    )}
                  </button>
                </div>
              </div>
            </div>
          </div>
        </div>

        {/* Stats bar */}
        <div className="px-6 pb-3 flex items-center gap-4">
          {isInitialCommit ? (
            <span className="text-sm text-muted-foreground">
              Initial commit — all files are new
            </span>
          ) : (
            <>
              <span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-500/10 text-green-600 border border-green-500/20">
                <FilePlus className="h-3.5 w-3.5" />
                {totalAdditions} additions
              </span>
              <span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-500/10 text-red-600 border border-red-500/20">
                <FileX className="h-3.5 w-3.5" />
                {totalDeletions} deletions
              </span>
            </>
          )}
          <span className="text-sm text-muted-foreground">
            {filteredFiles.length} file{filteredFiles.length !== 1 ? "s" : ""} changed
          </span>
        </div>
      </div>

      {/* Main content */}
      <div className="flex-1 flex overflow-hidden">
        {/* Left: file list */}
        <div className="w-80 flex-shrink-0 border-r flex flex-col bg-muted/20">
          {/* Search */}
          <div className="p-3 border-b border-border">
            <div className="relative">
              <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
              <input
                type="text"
                placeholder="Filter files..."
                value={searchTerm}
                onChange={handleSearchChange}
                className="w-full pl-9 pr-3 py-2 h-9 text-sm border border-input rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-ring"
              />
            </div>
          </div>

          {/* File list */}
          <div className="flex-1 overflow-y-auto">
            {isInitialCommit ? (
              <div className="p-4 text-center text-muted-foreground text-sm">
                Initial commit — all files listed below
              </div>
            ) : filteredFiles.length === 0 ? (
              <div className="p-4 text-center text-muted-foreground text-sm">
                {searchTerm ? "No matching files" : "No changes"}
              </div>
            ) : (
              filteredFiles.map((file: DiffDelta, idx: number) => (
                <FileListItem
                  key={`${file.old_file.path ?? ""}-${file.new_file.path ?? ""}-${idx}`}
                  file={file}
                  isSelected={idx === safeSelectedIdx}
                  onClick={() => setSelectedFileIdx(idx)}
                />
              ))
            )}
          </div>
        </div>

        {/* Right: diff viewer */}
        <div className="flex-1 overflow-hidden flex flex-col bg-background">
          {diffFetching && !diffLoading && (
            <div className="flex items-center justify-center py-2 border-b border-border bg-muted/30">
              <Loader2 className="h-4 w-4 animate-spin text-muted-foreground mr-2" />
              <span className="text-sm text-muted-foreground">Updating...</span>
            </div>
          )}

          {isInitialCommit ? (
            <div className="flex-1 flex items-center justify-center">
              <div className="text-center text-muted-foreground">
                <FilePlus className="h-12 w-12 mx-auto mb-4 opacity-50" />
                <p>This is the initial commit</p>
                <p className="text-sm mt-1">All files were added</p>
              </div>
            </div>
          ) : currentFile ? (
            <DiffViewer file={currentFile} />
          ) : (
            <div className="flex-1 flex items-center justify-center">
              <div className="text-center text-muted-foreground">
                <File className="h-12 w-12 mx-auto mb-4 opacity-50" />
                <p>Select a file to view changes</p>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
};
