// @vitest-environment jsdom
import React from "react";
import { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { NextIntlClientProvider } from "next-intl";

import messages from "../../i18n/messages/en.json";

const cleanupCallbacks: Array<() => void> = [];

function makeContainer(): HTMLElement {
  const container = document.createElement("div");
  document.body.appendChild(container);
  cleanupCallbacks.push(() => {
    container.remove();
  });
  return container;
}

describe("DistributeProxiesButton", () => {
  beforeEach(() => {
    (
      globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
    ).IS_REACT_ACT_ENVIRONMENT = true;
    vi.useFakeTimers({ shouldAdvanceTime: true });
  });

  afterEach(() => {
    while (cleanupCallbacks.length > 0) {
      cleanupCallbacks.pop()?.();
    }
    document.body.innerHTML = "";
    vi.useRealTimers();
    vi.clearAllMocks();
  });

  async function renderButton(
    props: Partial<React.ComponentProps<typeof import("./DistributeProxiesButton").default>> = {}
  ) {
    const { default: DistributeProxiesButton } = await import(
      "./DistributeProxiesButton.tsx"
    );
    const container = makeContainer();
    const root = createRoot(container);
    await act(async () => {
      root.render(
        <NextIntlClientProvider
          locale="en"
          messages={{ sharedComponents: messages.sharedComponents }}
        >
          <DistributeProxiesButton
            onDistribute={props.onDistribute ?? vi.fn().mockResolvedValue(undefined)}
            {...props}
          />
        </NextIntlClientProvider>
      );
    });
    return { container, root };
  }

  it("renders with default label", async () => {
    const { container } = await renderButton();
    const button = container.querySelector("button");
    expect(button).not.toBeNull();
    expect(button?.textContent).toContain("Distribute Proxies");
  });

  it("renders with custom label", async () => {
    const { container } = await renderButton({ label: "Custom Label" });
    const button = container.querySelector("button");
    expect(button?.textContent).toContain("Custom Label");
  });

  it("shows swap_horiz icon in idle state", async () => {
    const { container } = await renderButton();
    const icon = container.querySelector(".material-symbols-outlined");
    expect(icon?.textContent).toBe("swap_horiz");
  });

  it("disables button when disabled prop is true", async () => {
    const { container } = await renderButton({ disabled: true });
    const button = container.querySelector("button") as HTMLButtonElement;
    expect(button.disabled).toBe(true);
  });

  it("calls onDistribute when clicked", async () => {
    const onDistribute = vi.fn().mockResolvedValue(undefined);
    const { container } = await renderButton({ onDistribute });
    const button = container.querySelector("button") as HTMLButtonElement;

    await act(async () => {
      button.click();
    });

    expect(onDistribute).toHaveBeenCalledTimes(1);
  });

  it("enters distributing state on click", async () => {
    let resolveDistribute: () => void;
    const onDistribute = vi.fn().mockImplementation(
      () => new Promise<void>((resolve) => { resolveDistribute = resolve; })
    );
    const { container } = await renderButton({ onDistribute });
    const button = container.querySelector("button") as HTMLButtonElement;

    await act(async () => {
      button.click();
    });

    // Should show distributing state
    expect(button.textContent).toContain("Distributing...");
    expect(button.disabled).toBe(true);
    const icon = container.querySelector(".material-symbols-outlined");
    expect(icon?.textContent).toBe("sync");

    // Resolve the promise
    await act(async () => {
      resolveDistribute!();
    });
  });

  it("enters complete state after successful distribution", async () => {
    vi.useFakeTimers({ shouldAdvanceTime: true });
    const onDistribute = vi.fn().mockResolvedValue(undefined);
    const { container } = await renderButton({ onDistribute });
    const button = container.querySelector("button") as HTMLButtonElement;

    await act(async () => {
      button.click();
    });

    // Wait for distributing to complete
    await act(async () => {
      await vi.advanceTimersByTimeAsync(100);
    });

    expect(button.textContent).toContain("Complete");
    const icon = container.querySelector(".material-symbols-outlined");
    expect(icon?.textContent).toBe("check");
  });

  it("returns to idle state after complete timeout", async () => {
    vi.useFakeTimers({ shouldAdvanceTime: true });
    const onDistribute = vi.fn().mockResolvedValue(undefined);
    const { container } = await renderButton({ onDistribute });
    const button = container.querySelector("button") as HTMLButtonElement;

    await act(async () => {
      button.click();
    });

    // Wait for distributing to complete
    await act(async () => {
      await vi.advanceTimersByTimeAsync(100);
    });

    expect(button.textContent).toContain("Complete");

    // Wait for the 1.5s timeout
    await act(async () => {
      await vi.advanceTimersByTimeAsync(1600);
    });

    expect(button.textContent).toContain("Distribute Proxies");
    const icon = container.querySelector(".material-symbols-outlined");
    expect(icon?.textContent).toBe("swap_horiz");
  });

  it("returns to idle state on error", async () => {
    const onDistribute = vi.fn().mockRejectedValue(new Error("fail"));
    const { container } = await renderButton({ onDistribute });
    const button = container.querySelector("button") as HTMLButtonElement;

    await act(async () => {
      button.click();
    });

    // Wait for error handling
    await act(async () => {
      await vi.advanceTimersByTimeAsync(100);
    });

    expect(button.textContent).toContain("Distribute Proxies");
    expect(button.disabled).toBe(false);
  });

  it("does not click when disabled", async () => {
    const onDistribute = vi.fn().mockResolvedValue(undefined);
    const { container } = await renderButton({ onDistribute, disabled: true });
    const button = container.querySelector("button") as HTMLButtonElement;

    await act(async () => {
      button.click();
    });

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

  it("applies sm size classes", async () => {
    const { container } = await renderButton({ size: "sm" });
    const button = container.querySelector("button") as HTMLButtonElement;
    expect(button.className).toContain("px-2");
    expect(button.className).toContain("py-1");
    expect(button.className).toContain("text-[11px]");
  });

  it("applies md size classes by default", async () => {
    const { container } = await renderButton();
    const button = container.querySelector("button") as HTMLButtonElement;
    expect(button.className).toContain("px-3");
    expect(button.className).toContain("py-1.5");
    expect(button.className).toContain("text-xs");
  });

  it("sets aria-label to the current label", async () => {
    const { container } = await renderButton();
    const button = container.querySelector("button") as HTMLButtonElement;
    expect(button.getAttribute("aria-label")).toBe("Distribute Proxies");
  });

  it("sets title to the current label", async () => {
    const { container } = await renderButton();
    const button = container.querySelector("button") as HTMLButtonElement;
    expect(button.getAttribute("title")).toBe("Distribute Proxies");
  });
});
