"use client";

import { useState, useEffect, useRef, useCallback } from "react";
import { Clock, Loader2, GitCommit, X, FileText } from "lucide-react";
import { fetchDocumentHistory, fetchCommitDetail, fetchFileContent } from "@/lib/api";

interface CommitHistoryProps {
  vaultFullName: string;
  path: string;
  diffFilePath?: string;
}

function formatRelativeTime(dateStr: string): string {
  const now = Date.now();
  const then = new Date(dateStr).getTime();
  const diffMs = now - then;
  const diffMin = Math.floor(diffMs / 60000);
  if (diffMin < 1) return "just now";
  if (diffMin < 60) return `${diffMin}m ago`;
  const diffHr = Math.floor(diffMin / 60);
  if (diffHr < 24) return `${diffHr}h ago`;
  const diffDays = Math.floor(diffHr / 24);
  if (diffDays === 1) return "yesterday";
  if (diffDays < 30) return `${diffDays}d ago`;
  return new Date(dateStr).toLocaleDateString("en-US", { month: "short", day: "numeric" });
}

// Simple LCS diff — fallback when server doesn't return patch
function computeDiff(oldText: string, newText: string): { type: "same" | "add" | "del"; line: string }[] {
  const oldLines = oldText.split("\n");
  const newLines = newText.split("\n");
  const m = oldLines.length;
  const n = newLines.length;
  const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
  for (let i = 1; i <= m; i++)
    for (let j = 1; j <= n; j++)
      dp[i][j] = oldLines[i - 1] === newLines[j - 1] ? dp[i - 1][j - 1] + 1 : Math.max(dp[i - 1][j], dp[i][j - 1]);

  const lines: { type: "same" | "add" | "del"; line: string }[] = [];
  let i = m, j = n;
  while (i > 0 || j > 0) {
    if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
      lines.push({ type: "same", line: oldLines[i - 1] }); i--; j--;
    } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
      lines.push({ type: "add", line: newLines[j - 1] }); j--;
    } else {
      lines.push({ type: "del", line: oldLines[i - 1] }); i--;
    }
  }
  lines.reverse();
  const ctx = 3;
  const show = new Set<number>();
  for (let idx = 0; idx < lines.length; idx++) {
    if (lines[idx].type !== "same") {
      for (let c = Math.max(0, idx - ctx); c <= Math.min(lines.length - 1, idx + ctx); c++) show.add(c);
    }
  }
  return lines.filter((_, idx) => show.has(idx));
}

// Parse unified diff patch string into displayable lines
function parsePatch(patch: string): { type: "same" | "add" | "del" | "hunk"; line: string }[] {
  return patch.split("\n").map((line) => {
    if (line.startsWith("@@")) return { type: "hunk" as const, line };
    if (line.startsWith("+")) return { type: "add" as const, line: line.slice(1) };
    if (line.startsWith("-")) return { type: "del" as const, line: line.slice(1) };
    return { type: "same" as const, line: line.startsWith(" ") ? line.slice(1) : line };
  });
}

interface FileDiff {
  filename: string;
  status: string;
  additions: number;
  deletions: number;
  lines: { type: "same" | "add" | "del" | "hunk"; line: string }[];
}

interface DiffViewState {
  sha: string;
  message: string;
  fileDiffs: FileDiff[];
}

export function CommitHistory({ vaultFullName, path, diffFilePath }: CommitHistoryProps) {
  const [open, setOpen] = useState(false);
  const [commits, setCommits] = useState<any[]>([]);
  const [loading, setLoading] = useState(false);
  const [diffView, setDiffView] = useState<DiffViewState | null>(null);
  const [loadingDiff, setLoadingDiff] = useState(false);
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!open) return;
    let cancelled = false;
    setLoading(true);
    fetchDocumentHistory(vaultFullName, path).then((result) => {
      if (!cancelled) { setCommits(result); setLoading(false); }
    });
    return () => { cancelled = true; };
  }, [open, vaultFullName, path]);

  useEffect(() => {
    if (!open || diffView) return;
    const handler = (e: MouseEvent) => {
      if (containerRef.current && !containerRef.current.contains(e.target as Node)) setOpen(false);
    };
    document.addEventListener("mousedown", handler);
    return () => document.removeEventListener("mousedown", handler);
  }, [open, diffView]);

  const handleClickCommit = useCallback(async (commitIdx: number) => {
    const commit = commits[commitIdx];
    if (!commit?.sha) return;

    setLoadingDiff(true);
    setDiffView(null);

    // Use server-side files[] from commit detail API
    const detail = await fetchCommitDetail(vaultFullName, commit.sha);

    if (detail && detail.files.length > 0) {
      // Server returned changed files list
      const fileDiffs: FileDiff[] = [];

      for (const f of detail.files) {
        if (f.patch) {
          // Server returned unified diff — parse it directly
          fileDiffs.push({
            filename: f.filename,
            status: f.status,
            additions: f.additions,
            deletions: f.deletions,
            lines: parsePatch(f.patch),
          });
        } else {
          // No patch — fallback: fetch content at both refs and compute diff client-side
          const prevCommit = commits[commitIdx + 1];
          const newContent = await fetchFileContent(vaultFullName, f.filename);
          const oldContent = prevCommit?.sha
            ? await fetchFileAtRef(vaultFullName, f.filename, prevCommit.sha)
            : null;

          // Skip if content is identical (shouldn't happen but safe)
          if (oldContent === newContent && f.status === "modified") continue;

          const lines = computeDiff(oldContent || "", newContent || "");
          if (lines.length === 0 && f.status === "modified") continue;

          fileDiffs.push({
            filename: f.filename,
            status: f.status,
            additions: f.additions,
            deletions: f.deletions,
            lines,
          });
        }
      }

      setDiffView({ sha: commit.sha, message: detail.message, fileDiffs });
    } else {
      // Fallback: server didn't return files[] — diff single file
      const filePath = diffFilePath || path;
      const prevCommit = commits[commitIdx + 1];
      const newContent = await fetchFileContent(vaultFullName, filePath);
      const oldContent = prevCommit?.sha
        ? await fetchFileAtRef(vaultFullName, filePath, prevCommit.sha)
        : null;
      const lines = computeDiff(oldContent || "", newContent || "");

      setDiffView({
        sha: commit.sha,
        message: commit.message || commit.sha.slice(0, 7),
        fileDiffs: lines.length > 0 ? [{
          filename: filePath.split("/").pop() || filePath,
          status: "modified",
          additions: lines.filter(l => l.type === "add").length,
          deletions: lines.filter(l => l.type === "del").length,
          lines,
        }] : [],
      });
    }

    setLoadingDiff(false);
  }, [commits, vaultFullName, path, diffFilePath]);

  // --- Diff modal ---
  if (diffView) {
    const totalAdd = diffView.fileDiffs.reduce((s, f) => s + f.additions, 0);
    const totalDel = diffView.fileDiffs.reduce((s, f) => s + f.deletions, 0);

    return (
      <>
        <button
          onClick={() => setOpen(!open)}
          className="flex items-center gap-1.5 px-2.5 py-1.5 text-xs rounded-lg border border-white/10 text-gray-400 hover:text-white hover:bg-white/5 transition-colors"
        >
          <Clock size={12} />
          History
        </button>
        <div className="fixed inset-0 z-[70] flex items-center justify-center bg-black/60 backdrop-blur-sm">
          <div className="w-full max-w-3xl max-h-[85vh] rounded-xl border border-white/10 bg-[#10141a] shadow-2xl flex flex-col overflow-hidden">
            <div className="px-5 py-3 border-b border-white/5 flex items-center justify-between shrink-0">
              <div className="min-w-0">
                <div className="text-sm text-white font-medium truncate">{diffView.message}</div>
                <div className="flex items-center gap-3 mt-1">
                  <span className="text-[10px] font-mono text-[#58a6ff]">{diffView.sha.slice(0, 7)}</span>
                  <span className="text-[10px] text-gray-500">
                    {diffView.fileDiffs.length} file{diffView.fileDiffs.length !== 1 ? "s" : ""} changed
                  </span>
                  {totalAdd > 0 && <span className="text-[10px] text-[#67df70]">+{totalAdd}</span>}
                  {totalDel > 0 && <span className="text-[10px] text-red-400">-{totalDel}</span>}
                </div>
              </div>
              <button onClick={() => setDiffView(null)} className="text-gray-400 hover:text-white p-1">
                <X size={16} />
              </button>
            </div>

            <div className="flex-1 overflow-y-auto">
              {diffView.fileDiffs.length === 0 ? (
                <div className="py-8 text-center text-gray-500 text-sm">No changes detected.</div>
              ) : (
                diffView.fileDiffs.map((fd, fi) => (
                  <div key={fi} className="border-b border-white/5 last:border-0">
                    <div className="px-4 py-2 bg-[#1c2026] flex items-center gap-2 sticky top-0 z-10">
                      <FileText size={12} className="text-gray-400 shrink-0" />
                      <span className="text-xs font-mono text-gray-200">{fd.filename}</span>
                      <span className={`text-[10px] font-mono ml-auto ${
                        fd.status === "added" ? "text-[#67df70]" : fd.status === "removed" || fd.status === "deleted" ? "text-red-400" : "text-gray-500"
                      }`}>
                        {fd.status}
                      </span>
                      {fd.additions > 0 && <span className="text-[10px] text-[#67df70]">+{fd.additions}</span>}
                      {fd.deletions > 0 && <span className="text-[10px] text-red-400">-{fd.deletions}</span>}
                    </div>
                    <div className="font-mono text-xs">
                      {fd.lines.map((l, li) => (
                        <div
                          key={li}
                          className={
                            l.type === "add" ? "bg-[#67df70]/8 text-[#67df70] px-4 py-px"
                            : l.type === "del" ? "bg-red-500/8 text-red-400 px-4 py-px"
                            : l.type === "hunk" ? "bg-[#58a6ff]/10 text-[#58a6ff] px-4 py-0.5 text-[10px]"
                            : "text-gray-600 px-4 py-px"
                          }
                        >
                          <span className="inline-block w-4 text-right mr-3 select-none opacity-50">
                            {l.type === "add" ? "+" : l.type === "del" ? "−" : l.type === "hunk" ? "@@" : " "}
                          </span>
                          {l.line || " "}
                        </div>
                      ))}
                    </div>
                  </div>
                ))
              )}
            </div>
          </div>
        </div>
      </>
    );
  }

  // --- Commit list dropdown ---
  return (
    <div className="relative" ref={containerRef}>
      <button
        onClick={() => setOpen(!open)}
        className="flex items-center gap-1.5 px-2.5 py-1.5 text-xs rounded-lg border border-white/10 text-gray-400 hover:text-white hover:bg-white/5 transition-colors"
      >
        <Clock size={12} />
        History
      </button>

      {open && (
        <div className="absolute right-0 top-full mt-2 w-80 rounded-xl border border-white/10 bg-[#1c2026] shadow-xl z-50 overflow-hidden">
          <div className="px-4 py-3 border-b border-white/5 flex items-center gap-2">
            <GitCommit size={13} className="text-gray-400" />
            <span className="text-xs font-mono uppercase tracking-wider text-gray-400">Commits</span>
          </div>

          <div className="max-h-64 overflow-y-auto">
            {loading || loadingDiff ? (
              <div className="flex items-center gap-2 py-6 justify-center text-gray-500 text-sm">
                <Loader2 size={14} className="animate-spin" />
                {loadingDiff ? "Loading diff..." : ""}
              </div>
            ) : commits.length === 0 ? (
              <p className="text-xs text-gray-500 py-6 text-center">No commits found.</p>
            ) : (
              commits.map((commit, i) => (
                <button
                  key={commit.sha || i}
                  onClick={() => handleClickCommit(i)}
                  className="w-full text-left px-4 py-2.5 border-b border-white/5 last:border-0 hover:bg-white/5 transition-colors"
                >
                  <p className="text-sm text-gray-200 truncate">
                    {commit.message || commit.sha?.slice(0, 7)}
                  </p>
                  <div className="flex items-center justify-between mt-1">
                    <div className="flex items-center gap-2">
                      <span className="text-[10px] font-mono text-[#58a6ff]">
                        {commit.sha?.slice(0, 7)}
                      </span>
                      <span className="text-[10px] text-gray-500">
                        {commit.author || "unknown"}
                      </span>
                    </div>
                    <span className="text-[10px] text-gray-500">
                      {commit.date ? formatRelativeTime(commit.date) : ""}
                    </span>
                  </div>
                </button>
              ))
            )}
          </div>
        </div>
      )}
    </div>
  );
}

// Helper for fetching file at specific ref (fallback when no patch)
async function fetchFileAtRef(vaultFullName: string, filePath: string, ref: string): Promise<string | null> {
  try {
    const parts = vaultFullName.split("/");
    const owner = parts[0];
    const repo = parts.slice(1).join("/");
    const auth = JSON.parse(localStorage.getItem("knowhub_auth") || "{}");
    const res = await fetch(`/api/proxy/repos/${owner}/${repo}/contents/${filePath}?ref=${ref}`, {
      headers: { "X-KnowHub-Server": auth.serverUrl || "", "X-KnowHub-Token": auth.token || "" },
    });
    if (!res.ok) return null;
    const data = await res.json();
    if (!data?.content) return null;
    return atob(data.content.replace(/\n/g, ""));
  } catch { return null; }
}
