// @vitest-environment happy-dom
import "@testing-library/jest-dom/vitest";
import { render, screen, act } from "@testing-library/react";
import { vi, describe, it, expect, beforeEach } from "vitest";

// ── Capture props passed to stubs ─────────────────────────────────────────────
// These are module-level so the vi.mock factories (which are hoisted) can close over them.
let diffViewerProps: Record<string, unknown> = {};
let reviewSidebarProps: Record<string, unknown> = {};

vi.mock("./DiffViewer", () => ({
  DiffViewer: (props: Record<string, unknown>) => {
    diffViewerProps = props;
    return <div data-testid="diff-viewer" />;
  },
}));

vi.mock("./ReviewSidebar", () => ({
  ReviewSidebar: (props: Record<string, unknown>) => {
    reviewSidebarProps = props;
    return <div data-testid="review-sidebar" />;
  },
}));

vi.mock("./ReviewHeader", () => ({
  ReviewHeader: (props: Record<string, unknown>) => (
    <div data-testid="review-header">
      {props.onExpandAll && (
        <button
          data-testid="expand-all-btn"
          onClick={props.onExpandAll as () => void}
        >
          Expand all
        </button>
      )}
    </div>
  ),
}));

vi.mock("./InitiativeReview", () => ({
  InitiativeReview: () => <div data-testid="initiative-review" />,
}));

vi.mock("./comment-index", () => ({
  buildCommentIndex: vi.fn(() => new Map()),
}));

vi.mock("sonner", () => ({
  toast: { success: vi.fn(), error: vi.fn() },
}));

// ── parseUnifiedDiff spy ───────────────────────────────────────────────────────
const mockParseUnifiedDiff = vi.fn((_raw: string) => [
  {
    oldPath: "a.ts",
    newPath: "a.ts",
    status: "modified" as const,
    additions: 3,
    deletions: 1,
    hunks: [],
  },
]);

vi.mock("./parse-diff", () => ({
  get parseUnifiedDiff() {
    return mockParseUnifiedDiff;
  },
}));

// ── tRPC mock factory ─────────────────────────────────────────────────────────

const noopMutation = () => ({
  mutate: vi.fn(),
  isPending: false,
});

const noopQuery = (data: unknown = undefined) => ({
  data,
  isLoading: false,
  isError: false,
  refetch: vi.fn(),
});

const mockUtils = {
  listReviewComments: { invalidate: vi.fn() },
};

// Server format (FileStatEntry): uses `path` not `newPath`
const PHASE_FILES = [
  {
    path: "a.ts",
    status: "modified" as const,
    additions: 5,
    deletions: 2,
  },
];

// trpcMock is a let so tests can override it. The getter in the mock reads the current value.
let trpcMock = buildTrpcMock();

function buildTrpcMock(overrides: Record<string, unknown> = {}) {
  return {
    getInitiative: { useQuery: vi.fn(() => noopQuery({ status: "in_progress" })) },
    listPhases: {
      useQuery: vi.fn(() =>
        noopQuery([{ id: "phase-1", name: "Phase 1", status: "pending_review" }])
      ),
    },
    getInitiativeProjects: { useQuery: vi.fn(() => noopQuery([{ id: "proj-1" }])) },
    getPhaseReviewDiff: {
      useQuery: vi.fn(() =>
        noopQuery({
          phaseName: "Phase 1",
          sourceBranch: "cw/phase-1",
          targetBranch: "main",
          files: PHASE_FILES,
          totalAdditions: 5,
          totalDeletions: 2,
        })
      ),
    },
    getPhaseReviewCommits: {
      useQuery: vi.fn(() =>
        noopQuery({ commits: [], sourceBranch: "cw/phase-1", targetBranch: "main" })
      ),
    },
    getCommitDiff: {
      useQuery: vi.fn(() => noopQuery({ rawDiff: "" })),
    },
    listPreviews: { useQuery: vi.fn(() => noopQuery([])) },
    getPreviewStatus: { useQuery: vi.fn(() => noopQuery(null)) },
    listReviewComments: { useQuery: vi.fn(() => noopQuery([])) },
    startPreview: { useMutation: vi.fn(() => noopMutation()) },
    stopPreview: { useMutation: vi.fn(() => noopMutation()) },
    createReviewComment: { useMutation: vi.fn(() => noopMutation()) },
    resolveReviewComment: { useMutation: vi.fn(() => noopMutation()) },
    unresolveReviewComment: { useMutation: vi.fn(() => noopMutation()) },
    replyToReviewComment: { useMutation: vi.fn(() => noopMutation()) },
    updateReviewComment: { useMutation: vi.fn(() => noopMutation()) },
    approvePhaseReview: { useMutation: vi.fn(() => noopMutation()) },
    requestPhaseChanges: { useMutation: vi.fn(() => noopMutation()) },
    useUtils: vi.fn(() => mockUtils),
    ...overrides,
  };
}

vi.mock("@/lib/trpc", () => ({
  get trpc() {
    return trpcMock;
  },
}));

// ── Import component after mocks ──────────────────────────────────────────────
import { ReviewTab } from "./ReviewTab";

// ── Tests ─────────────────────────────────────────────────────────────────────

describe("ReviewTab", () => {
  beforeEach(() => {
    diffViewerProps = {};
    reviewSidebarProps = {};
    mockParseUnifiedDiff.mockClear();
    trpcMock = buildTrpcMock();
  });

  it("1. phase diff loads metadata: DiffViewer receives files array and commitMode=false", () => {
    render(<ReviewTab initiativeId="init-1" />);

    expect(screen.getByTestId("diff-viewer")).toBeInTheDocument();
    const files = diffViewerProps.files as unknown[];
    expect(files).toHaveLength(1);
    expect(diffViewerProps.commitMode).toBe(false);
  });

  it("2. no rawDiff parsing in phase mode: parseUnifiedDiff is NOT called", () => {
    render(<ReviewTab initiativeId="init-1" />);

    expect(mockParseUnifiedDiff).not.toHaveBeenCalled();
  });

  it("3. commit view parses rawDiff: parseUnifiedDiff called and DiffViewer gets commitMode=true", async () => {
    trpcMock = buildTrpcMock({
      getCommitDiff: {
        useQuery: vi.fn(() =>
          noopQuery({ rawDiff: "diff --git a/a.ts b/a.ts\nindex 000..111 100644\n--- a/a.ts\n+++ b/a.ts\n@@ -1,1 +1,1 @@\n-old\n+new\n" })
        ),
      },
    });

    render(<ReviewTab initiativeId="init-1" />);

    // Select a commit via the sidebar stub's onSelectCommit prop
    const { onSelectCommit } = reviewSidebarProps as {
      onSelectCommit: (hash: string | null) => void;
    };

    await act(async () => {
      onSelectCommit("abc123");
    });

    expect(diffViewerProps.commitMode).toBe(true);
    expect(mockParseUnifiedDiff).toHaveBeenCalled();
  });

  it("4. allFiles uses metadata for sidebar: ReviewSidebar receives files from diffQuery.data.files", () => {
    render(<ReviewTab initiativeId="init-1" />);

    const sidebarFiles = reviewSidebarProps.files as Array<{ newPath: string }>;
    expect(sidebarFiles).toHaveLength(1);
    expect(sidebarFiles[0].newPath).toBe("a.ts");
  });

  it("5. expandAll prop passed: clicking Expand all button causes DiffViewer to receive expandAll=true", async () => {
    render(<ReviewTab initiativeId="init-1" />);

    // Before clicking, expandAll should be false
    expect(diffViewerProps.expandAll).toBe(false);

    const expandBtn = screen.getByTestId("expand-all-btn");
    await act(async () => {
      expandBtn.click();
    });

    expect(diffViewerProps.expandAll).toBe(true);
  });
});
