import { describe, it, expect, beforeEach, vi } from "vitest";

vi.mock("@/lib/db", () => ({
  db: {
    user: {
      findUnique: vi.fn(),
    },
  },
}));

vi.mock("@/lib/auth", () => ({
  auth: vi.fn(),
}));

vi.mock("@/lib/stripe", () => ({
  stripe: {
    transfers: {
      create: vi.fn(),
    },
  },
}));

const mockGetPendingPayout = vi.fn();
const mockMarkEventsPaidOut = vi.fn();
vi.mock("@/lib/marketplace-billing", () => ({
  getPendingPayout: (...args: unknown[]) => mockGetPendingPayout(...args),
  markEventsPaidOut: (...args: unknown[]) => mockMarkEventsPaidOut(...args),
  MIN_PAYOUT_CENTS: 100,
}));

vi.mock("@/lib/logger", () => ({
  logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn() },
}));

import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
import { stripe } from "@/lib/stripe";

describe("POST /api/stripe/connect/payout", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("returns 401 when not authenticated", async () => {
    vi.mocked(auth).mockResolvedValue(null as never);

    const { POST } = await import(
      "@/app/api/stripe/connect/payout/route"
    );
    const response = await POST();

    expect(response.status).toBe(401);
  });

  it("returns 400 when user has no Connect account", async () => {
    vi.mocked(auth).mockResolvedValue({ user: { id: "user-1" } } as never);
    vi.mocked(db.user.findUnique).mockResolvedValue({
      id: "user-1",
      stripeConnectAccountId: null,
      stripeConnectOnboardingComplete: false,
    } as never);

    const { POST } = await import(
      "@/app/api/stripe/connect/payout/route"
    );
    const response = await POST();
    const data = await response.json();

    expect(response.status).toBe(400);
    expect(data.error).toContain("No Stripe Connect account");
  });

  it("returns 400 when onboarding is not complete", async () => {
    vi.mocked(auth).mockResolvedValue({ user: { id: "user-1" } } as never);
    vi.mocked(db.user.findUnique).mockResolvedValue({
      id: "user-1",
      stripeConnectAccountId: "acct_123",
      stripeConnectOnboardingComplete: false,
    } as never);

    const { POST } = await import(
      "@/app/api/stripe/connect/payout/route"
    );
    const response = await POST();
    const data = await response.json();

    expect(response.status).toBe(400);
    expect(data.error).toContain("onboarding is not complete");
  });

  it("returns 400 when pending balance is below minimum", async () => {
    vi.mocked(auth).mockResolvedValue({ user: { id: "user-1" } } as never);
    vi.mocked(db.user.findUnique).mockResolvedValue({
      id: "user-1",
      stripeConnectAccountId: "acct_123",
      stripeConnectOnboardingComplete: true,
    } as never);
    mockGetPendingPayout.mockResolvedValue({
      pendingCents: 50,
      eventCount: 2,
      eventIds: ["evt-1", "evt-2"],
    });

    const { POST } = await import(
      "@/app/api/stripe/connect/payout/route"
    );
    const response = await POST();
    const data = await response.json();

    expect(response.status).toBe(400);
    expect(data.error).toContain("Minimum payout");
    expect(data.pendingCents).toBe(50);
    expect(data.minimumCents).toBe(100);
  });

  it("creates transfer and marks events as paid when balance meets minimum", async () => {
    vi.mocked(auth).mockResolvedValue({ user: { id: "user-1" } } as never);
    vi.mocked(db.user.findUnique).mockResolvedValue({
      id: "user-1",
      stripeConnectAccountId: "acct_123",
      stripeConnectOnboardingComplete: true,
    } as never);
    mockGetPendingPayout.mockResolvedValue({
      pendingCents: 500,
      eventCount: 5,
      eventIds: ["evt-1", "evt-2", "evt-3", "evt-4", "evt-5"],
    });
    vi.mocked(stripe!.transfers.create).mockResolvedValue({
      id: "tr_abc123",
      amount: 500,
    } as never);
    mockMarkEventsPaidOut.mockResolvedValue(5);

    const { POST } = await import(
      "@/app/api/stripe/connect/payout/route"
    );
    const response = await POST();
    const data = await response.json();

    expect(response.status).toBe(200);
    expect(data.transferId).toBe("tr_abc123");
    expect(data.amountCents).toBe(500);
    expect(data.amountDollars).toBe(5);
    expect(data.eventCount).toBe(5);

    // Verify Stripe transfer was created correctly
    expect(stripe!.transfers.create).toHaveBeenCalledWith({
      amount: 500,
      currency: "usd",
      destination: "acct_123",
      metadata: {
        userId: "user-1",
        eventCount: "5",
      },
    });

    // Verify events were marked as paid
    expect(mockMarkEventsPaidOut).toHaveBeenCalledWith(
      ["evt-1", "evt-2", "evt-3", "evt-4", "evt-5"],
      "tr_abc123",
    );
  });

  it("returns 500 on Stripe API error", async () => {
    vi.mocked(auth).mockResolvedValue({ user: { id: "user-1" } } as never);
    vi.mocked(db.user.findUnique).mockResolvedValue({
      id: "user-1",
      stripeConnectAccountId: "acct_123",
      stripeConnectOnboardingComplete: true,
    } as never);
    mockGetPendingPayout.mockResolvedValue({
      pendingCents: 500,
      eventCount: 5,
      eventIds: ["evt-1"],
    });
    vi.mocked(stripe!.transfers.create).mockRejectedValue(
      new Error("Stripe error"),
    );

    const { POST } = await import(
      "@/app/api/stripe/connect/payout/route"
    );
    const response = await POST();

    expect(response.status).toBe(500);
  });

  it("handles exact minimum payout amount ($1.00)", async () => {
    vi.mocked(auth).mockResolvedValue({ user: { id: "user-1" } } as never);
    vi.mocked(db.user.findUnique).mockResolvedValue({
      id: "user-1",
      stripeConnectAccountId: "acct_123",
      stripeConnectOnboardingComplete: true,
    } as never);
    mockGetPendingPayout.mockResolvedValue({
      pendingCents: 100,
      eventCount: 1,
      eventIds: ["evt-1"],
    });
    vi.mocked(stripe!.transfers.create).mockResolvedValue({
      id: "tr_min",
      amount: 100,
    } as never);
    mockMarkEventsPaidOut.mockResolvedValue(1);

    const { POST } = await import(
      "@/app/api/stripe/connect/payout/route"
    );
    const response = await POST();

    expect(response.status).toBe(200);
    const data = await response.json();
    expect(data.amountCents).toBe(100);
    expect(data.amountDollars).toBe(1);
  });
});
