import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { OperatorDashboard } from "@/app/dashboard/devices/operator/operator-dashboard";

// ---------------------------------------------------------------------------
// Mock data
// ---------------------------------------------------------------------------

const mockSettings = {
  marketplaceEnabled: true,
  pricePerMToken: 0.15,
  autoPrice: true,
  availableModels: ["qwen3:14b", "llama3.3:70b"],
};

const mockEarnings = {
  totalRevenue: 12.5,
  operatorShare: 10.0,
  totalRequests: 42,
  periodDays: 30,
  dailyAverage: 0.33,
};

const mockStripeConnected = {
  connected: true,
  accountId: "acct_123",
};

const mockStripeDisconnected = {
  connected: false,
};

const mockTermsAccepted = {
  accepted: true,
  acceptedAt: "2026-02-20T00:00:00.000Z",
};

const mockTermsNotAccepted = {
  accepted: false,
  acceptedAt: null,
};

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

type MockResponses = {
  settings?: object;
  earnings?: object;
  stripe?: object;
  terms?: object;
  onboard?: object;
  loginLink?: object;
  payout?: { status?: number; body: object };
  termsPost?: object;
  settingsPut?: object;
};

function createMockFetch(overrides: MockResponses = {}) {
  const settings = overrides.settings ?? mockSettings;
  const earnings = overrides.earnings ?? mockEarnings;
  const stripe = overrides.stripe ?? mockStripeConnected;
  const terms = overrides.terms ?? mockTermsAccepted;

  return vi.fn().mockImplementation((url: string, init?: RequestInit) => {
    const method = init?.method ?? "GET";

    // Settings GET
    if (
      typeof url === "string" &&
      url.includes("/api/dashboard/operator/settings") &&
      method === "GET"
    ) {
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve(settings),
      });
    }

    // Settings PUT
    if (
      typeof url === "string" &&
      url.includes("/api/dashboard/operator/settings") &&
      method === "PUT"
    ) {
      const body = overrides.settingsPut ?? settings;
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve(body),
      });
    }

    // Earnings
    if (
      typeof url === "string" &&
      url.includes("/api/dashboard/operator/earnings")
    ) {
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve(earnings),
      });
    }

    // Stripe connect status
    if (
      typeof url === "string" &&
      url.includes("/api/stripe/connect/status")
    ) {
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve(stripe),
      });
    }

    // Stripe onboard
    if (
      typeof url === "string" &&
      url.includes("/api/stripe/connect/onboard") &&
      method === "POST"
    ) {
      const body = overrides.onboard ?? {
        url: "https://connect.stripe.com/setup/abc",
      };
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve(body),
      });
    }

    // Stripe login link
    if (
      typeof url === "string" &&
      url.includes("/api/stripe/connect/login-link") &&
      method === "POST"
    ) {
      const body = overrides.loginLink ?? {
        url: "https://dashboard.stripe.com/login/abc",
      };
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve(body),
      });
    }

    // Stripe payout
    if (
      typeof url === "string" &&
      url.includes("/api/stripe/connect/payout") &&
      method === "POST"
    ) {
      const payout = overrides.payout ?? {
        status: 200,
        body: {
          transferId: "tr_123",
          amountCents: 800,
          amountDollars: 8.0,
          eventCount: 5,
        },
      };
      return Promise.resolve({
        ok: (payout.status ?? 200) === 200,
        json: () => Promise.resolve(payout.body),
      });
    }

    // Terms GET
    if (
      typeof url === "string" &&
      url.includes("/api/marketplace/terms") &&
      method === "GET"
    ) {
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve(terms),
      });
    }

    // Terms POST
    if (
      typeof url === "string" &&
      url.includes("/api/marketplace/terms") &&
      method === "POST"
    ) {
      const body = overrides.termsPost ?? {
        accepted: true,
        acceptedAt: new Date().toISOString(),
      };
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve(body),
      });
    }

    return Promise.resolve({
      ok: true,
      json: () => Promise.resolve({}),
    });
  });
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("OperatorDashboard", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  // -----------------------------------------------------------------------
  // Loading state
  // -----------------------------------------------------------------------

  it("shows loading skeleton initially", () => {
    vi.stubGlobal(
      "fetch",
      vi.fn().mockImplementation(
        () =>
          new Promise(() => {
            /* never resolves */
          }),
      ),
    );

    const { container } = render(<OperatorDashboard userId="user-1" />);
    const skeleton = container.querySelector(".animate-pulse");
    expect(skeleton).toBeTruthy();
  });

  // -----------------------------------------------------------------------
  // Earnings card
  // -----------------------------------------------------------------------

  it("renders earnings card with data", async () => {
    vi.stubGlobal("fetch", createMockFetch());

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("total-revenue").textContent).toBe("$12.50");
    });

    expect(screen.getByTestId("operator-share").textContent).toBe("$10.00");
    expect(screen.getByTestId("total-requests").textContent).toBe("42");
  });

  it("shows daily average and projected monthly earnings", async () => {
    vi.stubGlobal("fetch", createMockFetch());

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("daily-average")).toBeDefined();
    });

    // dailyAvg = 10 / 30 = 0.33
    expect(screen.getByTestId("daily-average").textContent).toBe("$0.33");
    // projected = 0.33 * 30 = 10.00
    expect(screen.getByTestId("projected-monthly").textContent).toBe("$10.00");
  });

  it("does not show earnings projections when period is 0", async () => {
    vi.stubGlobal(
      "fetch",
      createMockFetch({
        earnings: { ...mockEarnings, periodDays: 0, operatorShare: 0 },
      }),
    );

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("total-revenue")).toBeDefined();
    });

    expect(screen.queryByTestId("daily-average")).toBeNull();
    expect(screen.queryByTestId("projected-monthly")).toBeNull();
  });

  // -----------------------------------------------------------------------
  // Available models
  // -----------------------------------------------------------------------

  it("displays available models as tags when marketplace is enabled", async () => {
    vi.stubGlobal("fetch", createMockFetch());

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("model-tags")).toBeDefined();
    });

    expect(screen.getByText("qwen3:14b")).toBeDefined();
    expect(screen.getByText("llama3.3:70b")).toBeDefined();
  });

  it("hides available models when marketplace is disabled", async () => {
    vi.stubGlobal(
      "fetch",
      createMockFetch({
        settings: { ...mockSettings, marketplaceEnabled: false },
      }),
    );

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("stripe-connect-section")).toBeDefined();
    });

    expect(screen.queryByTestId("model-tags")).toBeNull();
  });

  it("hides available models when models list is empty", async () => {
    vi.stubGlobal(
      "fetch",
      createMockFetch({
        settings: { ...mockSettings, availableModels: [] },
      }),
    );

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("stripe-connect-section")).toBeDefined();
    });

    expect(screen.queryByTestId("model-tags")).toBeNull();
  });

  // -----------------------------------------------------------------------
  // Stripe Connect section
  // -----------------------------------------------------------------------

  it("shows Stripe Connect section with connected status", async () => {
    vi.stubGlobal("fetch", createMockFetch());

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByText("Connected")).toBeDefined();
    });

    expect(screen.getByTestId("stripe-dashboard-btn")).toBeDefined();
    expect(screen.getByTestId("request-payout-btn")).toBeDefined();
  });

  it("shows onboarding prompt when Stripe is not connected", async () => {
    vi.stubGlobal(
      "fetch",
      createMockFetch({ stripe: mockStripeDisconnected }),
    );

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(
        screen.getByText("Connect your bank account to receive payouts"),
      ).toBeDefined();
    });

    expect(screen.getByTestId("stripe-onboard-btn")).toBeDefined();
    expect(screen.queryByTestId("stripe-dashboard-btn")).toBeNull();
    expect(screen.queryByTestId("request-payout-btn")).toBeNull();
  });

  it("calls onboard API and redirects when Set up Stripe is clicked", async () => {
    const mockFetch = createMockFetch({ stripe: mockStripeDisconnected });
    vi.stubGlobal("fetch", mockFetch);

    // Mock window.location.href setter
    const locationHref = vi.spyOn(window, "location", "get").mockReturnValue({
      ...window.location,
      href: "",
    } as Location);
    const assignMock = vi.fn();
    Object.defineProperty(window, "location", {
      value: { ...window.location, href: "", assign: assignMock },
      writable: true,
      configurable: true,
    });

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("stripe-onboard-btn")).toBeDefined();
    });

    fireEvent.click(screen.getByTestId("stripe-onboard-btn"));

    await waitFor(() => {
      const calls = (mockFetch.mock.calls as string[][]).filter(
        (c) => typeof c[0] === "string" && c[0].includes("/api/stripe/connect/onboard"),
      );
      expect(calls.length).toBe(1);
    });

    locationHref.mockRestore();
  });

  it("calls login-link API when View Stripe Dashboard is clicked", async () => {
    const mockFetch = createMockFetch();
    vi.stubGlobal("fetch", mockFetch);
    const openSpy = vi.spyOn(window, "open").mockImplementation(() => null);

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("stripe-dashboard-btn")).toBeDefined();
    });

    fireEvent.click(screen.getByTestId("stripe-dashboard-btn"));

    await waitFor(() => {
      expect(openSpy).toHaveBeenCalledWith(
        "https://dashboard.stripe.com/login/abc",
        "_blank",
        "noopener,noreferrer",
      );
    });

    openSpy.mockRestore();
  });

  it("calls payout API and shows success message", async () => {
    const mockFetch = createMockFetch();
    vi.stubGlobal("fetch", mockFetch);

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("request-payout-btn")).toBeDefined();
    });

    fireEvent.click(screen.getByTestId("request-payout-btn"));

    await waitFor(() => {
      expect(screen.getByTestId("payout-message")).toBeDefined();
    });

    expect(screen.getByTestId("payout-message").textContent).toContain(
      "$8.00",
    );
    expect(screen.getByTestId("payout-message").textContent).toContain(
      "successfully",
    );
  });

  it("shows error message when payout fails", async () => {
    const mockFetch = createMockFetch({
      payout: {
        status: 400,
        body: { error: "Minimum payout is $1.00. Current pending: $0.50." },
      },
    });
    vi.stubGlobal("fetch", mockFetch);

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("request-payout-btn")).toBeDefined();
    });

    fireEvent.click(screen.getByTestId("request-payout-btn"));

    await waitFor(() => {
      expect(screen.getByTestId("payout-message")).toBeDefined();
    });

    expect(screen.getByTestId("payout-message").textContent).toContain(
      "Minimum payout",
    );
  });

  // -----------------------------------------------------------------------
  // Terms modal
  // -----------------------------------------------------------------------

  it("shows terms modal when enabling marketplace without accepted terms", async () => {
    vi.stubGlobal(
      "fetch",
      createMockFetch({
        settings: { ...mockSettings, marketplaceEnabled: false },
        terms: mockTermsNotAccepted,
      }),
    );

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("marketplace-toggle")).toBeDefined();
    });

    fireEvent.click(screen.getByTestId("marketplace-toggle"));

    await waitFor(() => {
      expect(screen.getByTestId("terms-modal")).toBeDefined();
    });

    expect(screen.getByText("Marketplace Terms")).toBeDefined();
    expect(screen.getByTestId("accept-terms-btn")).toBeDefined();
  });

  it("does not show terms modal when terms are already accepted", async () => {
    const mockFetch = createMockFetch({
      settings: { ...mockSettings, marketplaceEnabled: false },
      terms: mockTermsAccepted,
    });
    vi.stubGlobal("fetch", mockFetch);

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("marketplace-toggle")).toBeDefined();
    });

    fireEvent.click(screen.getByTestId("marketplace-toggle"));

    // Should NOT show modal -- terms already accepted
    // Instead it should call the settings API directly
    await waitFor(() => {
      const putCalls = mockFetch.mock.calls.filter(
        (c) =>
          typeof c[0] === "string" &&
          c[0].includes("/api/dashboard/operator/settings") &&
          c[1]?.method === "PUT",
      );
      expect(putCalls.length).toBe(1);
    });

    expect(screen.queryByTestId("terms-modal")).toBeNull();
  });

  it("closes terms modal when Cancel is clicked", async () => {
    vi.stubGlobal(
      "fetch",
      createMockFetch({
        settings: { ...mockSettings, marketplaceEnabled: false },
        terms: mockTermsNotAccepted,
      }),
    );

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("marketplace-toggle")).toBeDefined();
    });

    fireEvent.click(screen.getByTestId("marketplace-toggle"));

    await waitFor(() => {
      expect(screen.getByTestId("terms-modal")).toBeDefined();
    });

    fireEvent.click(screen.getByText("Cancel"));

    await waitFor(() => {
      expect(screen.queryByTestId("terms-modal")).toBeNull();
    });
  });

  it("accepts terms and enables marketplace when Accept & Enable is clicked", async () => {
    const mockFetch = createMockFetch({
      settings: { ...mockSettings, marketplaceEnabled: false },
      terms: mockTermsNotAccepted,
    });
    vi.stubGlobal("fetch", mockFetch);

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("marketplace-toggle")).toBeDefined();
    });

    // Click toggle -> shows terms modal
    fireEvent.click(screen.getByTestId("marketplace-toggle"));

    await waitFor(() => {
      expect(screen.getByTestId("accept-terms-btn")).toBeDefined();
    });

    // Accept terms
    fireEvent.click(screen.getByTestId("accept-terms-btn"));

    await waitFor(() => {
      // Terms POST should have been called
      const termsPostCalls = mockFetch.mock.calls.filter(
        (c) =>
          typeof c[0] === "string" &&
          c[0].includes("/api/marketplace/terms") &&
          c[1]?.method === "POST",
      );
      expect(termsPostCalls.length).toBe(1);
    });

    await waitFor(() => {
      // Settings PUT should have been called to enable marketplace
      const settingsPutCalls = mockFetch.mock.calls.filter(
        (c) =>
          typeof c[0] === "string" &&
          c[0].includes("/api/dashboard/operator/settings") &&
          c[1]?.method === "PUT",
      );
      expect(settingsPutCalls.length).toBe(1);
    });

    // Modal should be closed
    await waitFor(() => {
      expect(screen.queryByTestId("terms-modal")).toBeNull();
    });
  });

  // -----------------------------------------------------------------------
  // Toggle directly (no terms needed)
  // -----------------------------------------------------------------------

  it("toggles marketplace off directly without terms check", async () => {
    const mockFetch = createMockFetch({
      settings: { ...mockSettings, marketplaceEnabled: true },
      terms: mockTermsAccepted,
    });
    vi.stubGlobal("fetch", mockFetch);

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      const toggle = screen.getByTestId("marketplace-toggle");
      expect(toggle.getAttribute("aria-checked")).toBe("true");
    });

    fireEvent.click(screen.getByTestId("marketplace-toggle"));

    await waitFor(() => {
      const putCalls = mockFetch.mock.calls.filter(
        (c) =>
          typeof c[0] === "string" &&
          c[0].includes("/api/dashboard/operator/settings") &&
          c[1]?.method === "PUT",
      );
      expect(putCalls.length).toBe(1);
    });

    // No modal shown
    expect(screen.queryByTestId("terms-modal")).toBeNull();
  });

  // -----------------------------------------------------------------------
  // All four APIs called on load
  // -----------------------------------------------------------------------

  it("fetches settings, earnings, stripe status, and terms on mount", async () => {
    const mockFetch = createMockFetch();
    vi.stubGlobal("fetch", mockFetch);

    render(<OperatorDashboard userId="user-1" />);

    await waitFor(() => {
      expect(screen.getByTestId("total-revenue")).toBeDefined();
    });

    const urls = mockFetch.mock.calls.map((c) => c[0]);
    expect(urls).toContainEqual(
      expect.stringContaining("/api/dashboard/operator/settings"),
    );
    expect(urls).toContainEqual(
      expect.stringContaining("/api/dashboard/operator/earnings"),
    );
    expect(urls).toContainEqual(
      expect.stringContaining("/api/stripe/connect/status"),
    );
    expect(urls).toContainEqual(
      expect.stringContaining("/api/marketplace/terms"),
    );
  });
});
