import type { LabResourceItem } from "@/lib/api/labs";
import type { LabInstanceMeta, LabPart, LabStatusV2 } from "@/lib/api/labs.v2";
import {
  createV2ServerlessLabMeta,
  createV2ServerlessSandboxLabInstanceVariables,
  mockedGetLabFeedback,
  mockedV2GetLabInstance,
  mockedV2GetLabInstanceParts,
  mockedV2GetLabInstanceResources,
  mockedV2GetLabInstanceVariables,
  mockedV2GetLabMeta,
  mockLabsApi,
} from "@/testutils/mock-lab";
import { mockedGetMe, normalTestUser } from "@/testutils/mock-me-context";
import {
  mockedAppRouterInstance,
  mockedUseSearchParams,
  mockNextNavigationModule,
} from "@/testutils/mock-next-navigation";
import { ProvidersWrapper } from "@/testutils/providers";
import { enableActx, useActx } from "@/testutils/use-actx";
import { render } from "@testing-library/react";
import { urlQueryToSearchParams } from "next/dist/shared/lib/router/utils/querystring";
import type { ParsedUrlQuery } from "node:querystring";
import { expect, suite, test, vitest } from "vitest";

enableActx();
mockNextNavigationModule();
mockLabsApi();

const mockedLabContent = `guide content
\`\`\`
'$\{LAB:SERVERLESS_CLUSTER_PASSWORD}'
\`\`\`
> Do not inject variable with normal variable pattern $\{SERVERLESS_CLUSTER_PASSWORD}

\`\`\`mermaid
sequenceDiagram
    Alice->>John: Hello John, how are you?
    John-->>Alice: Great!
    Alice-)John: See you later!
\`\`\`

> [!NOTE]
> This is note content

### Title 1

### Title 2

#### Title 3

`;

function mockStandardData(
  urlQuery: ParsedUrlQuery = {},
  { labStatusOverride }: { labStatusOverride?: LabStatusV2 } = {}
) {
  const labInstanceMeta: LabInstanceMeta = {
    id: 1001,
    lab_code: "test_000",
    status: "running",
    creation_progress: null,
    progress: null,
    tidb_version: "",
    exited_at: null,
    cloud_provider: "aws",
    region_name: "tokyo",
    created_at: new Date().toISOString(),
    ready_at: new Date().toISOString(),
    tidb_environment: "TiDB Cloud Starter",
    ...(labStatusOverride as any),
  };

  const variables = createV2ServerlessSandboxLabInstanceVariables();
  const labMeta = createV2ServerlessLabMeta();
  const parts: LabPart[] = [
    {
      type: "markdown",
      title: "Guide",
      content: mockedLabContent,
      public: true,
    },
    {
      type: "markdown",
      title: "Module - 1",
      content: "module 1 content",
      public: true,
    },
    {
      type: "markdown",
      title: "Module - 2",
      content: "module 2 content",
      public: true,
    },
  ];

  const resources: LabResourceItem[] = [
    { Type: "Web SSH", Role: "test-ssh1", Endpoint: "about:blank" },
    { Type: "URL", Role: "test-url", Endpoint: "some:endpoint" },
    {
      Type: "Database",
      Role: "test-db",
      Environment: "env",
      Endpoint: "some:endpoint",
      Username: "user",
      Password: "pass",
      Port: "4000",
      "Cluster Name": "cluster",
    },
    {
      Type: "Virtual Machine",
      Role: "test-vm",
      "Private IP": "1.2.3.4",
      "Public IP": "5.6.7.8",
    },
  ];

  mockedUseSearchParams.mockReturnValue(
    new URLSearchParams(urlQueryToSearchParams(urlQuery)) as any
  );
  mockedGetMe.mockReturnValue(normalTestUser);

  mockedGetLabFeedback.mockReturnValue(Promise.reject(new Error("404")));

  mockedV2GetLabInstance.mockReturnValue(Promise.resolve(labInstanceMeta));
  mockedV2GetLabInstanceVariables.mockReturnValue(Promise.resolve(variables));
  mockedV2GetLabMeta.mockReturnValue(Promise.resolve(labMeta));
  mockedV2GetLabInstanceParts.mockReturnValue(Promise.resolve(parts));
  mockedV2GetLabInstanceResources.mockReturnValue(Promise.resolve(resources));

  return {
    labMeta,
    labInstanceMeta,
    parts,
    variables,
    resources,
  };
}

suite("Lab v2", () => {
  test("Markdown Content", { timeout: 10000 }, async () => {
    const { default: LabV2 } = await import("@/components/labs/lab-v2");
    const { labInstanceMeta, variables } = mockStandardData();
    const actx = useActx();

    const ui = render(<LabV2 instance={labInstanceMeta} />, {
      wrapper: ProvidersWrapper,
    });
    await actx(async () => {});

    // Assert variables are not escaped.
    expect(ui.container.querySelector("article code")).toHaveTextContent(
      `'${variables["SERVERLESS_CLUSTER_PASSWORD"]}'`
    );

    // Assert standard bash style variable not injected.
    expect(ui.container.querySelector("article blockquote")).toHaveTextContent(
      "Do not inject variable with normal variable pattern ${SERVERLESS_CLUSTER_PASSWORD}"
    );

    // Test mermaid
    await actx(async () => {
      await vitest.waitUntil(
        () => !ui.container.querySelector('[data-mermaid-initializing="true"]')
      );
    });
    expect(
      ui.container.querySelector(
        '[data-mermaid="true"]:not([data-mermaid-initializing="true"])'
      )
    ).toBeVisible();

    // Test GitHub Style Alerts
    expect(
      ui.container.querySelector('[data-alert-type="NOTE"]')
    ).toHaveTextContent("This is note content");
  });

  test("TOC", async () => {
    const { default: LabV2 } = await import("@/components/labs/lab-v2");
    const { labInstanceMeta } = mockStandardData();
    const actx = useActx();

    const ui = render(<LabV2 instance={labInstanceMeta} />, {
      wrapper: ProvidersWrapper,
    });
    await actx(async () => {});

    expect(ui.getByRole("button", { name: "Title 1" })).toHaveAttribute(
      "href",
      `/path/to/page?instance=1001#title-1-1`
    );
    expect(ui.getByRole("button", { name: "Title 1" })).not.toHaveClass("pl-1");
    expect(ui.getByRole("button", { name: "Title 2" })).toHaveAttribute(
      "href",
      `/path/to/page?instance=1001#title-2-1`
    );
    expect(ui.getByRole("button", { name: "Title 2" })).not.toHaveClass("pl-1");
    expect(ui.getByRole("button", { name: "Title 3" })).toHaveAttribute(
      "href",
      `/path/to/page?instance=1001#title-3-1`
    );
    expect(ui.getByRole("button", { name: "Title 3" })).toHaveClass("pl-1");
  });

  suite("States", () => {
    test("Creating", async () => {
      const { default: LabV2 } = await import("@/components/labs/lab-v2");
      const { labInstanceMeta } = mockStandardData(
        {},
        {
          labStatusOverride: {
            status: "creating",
            creation_progress: {
              completed: 5,
              total: 12,
            },
          },
        }
      );
      const actx = useActx();

      const ui = render(<LabV2 instance={labInstanceMeta} />, {
        wrapper: ProvidersWrapper,
      });
      await actx(async () => {});
      expect(ui.getByRole("button", { name: "Starting" })).toBeDisabled();
      expect(ui.queryByRole("button", { name: "Resources" })).toBeNull();
      expect(ui.queryByRole("button", { name: "Terminate" })).toBeNull();
      expect(ui.queryByRole("button", { name: "Web SSH" })).toBeNull();
    });

    test("Creating Failed", async () => {
      const { default: LabV2 } = await import("@/components/labs/lab-v2");
      const { labInstanceMeta } = mockStandardData(
        {},
        {
          labStatusOverride: {
            status: "creating_failed",
            creation_progress: null,
          },
        }
      );
      const actx = useActx();

      const ui = render(<LabV2 instance={labInstanceMeta} />, {
        wrapper: ProvidersWrapper,
      });
      await actx(async () => {});
      expect(
        ui.getByRole("heading", { name: "Failed to deploy the lab" })
      ).toBeVisible();
      expect(ui.getByRole("button", { name: "Terminate" })).not.toBeDisabled();
    });

    test("Timeout", async () => {
      const { default: LabV2 } = await import("@/components/labs/lab-v2");
      const { labInstanceMeta } = mockStandardData(
        {},
        {
          labStatusOverride: {
            status: "timeout",
            creation_progress: null,
          },
        }
      );
      const actx = useActx();

      const ui = render(<LabV2 instance={labInstanceMeta} />, {
        wrapper: ProvidersWrapper,
      });
      await actx(async () => {});
      expect(
        ui.getByRole("heading", {
          name: "This lab has been timeout, resources were destructed",
        })
      ).toBeVisible();
      expect(ui.getByRole("button", { name: "Terminate" })).not.toBeDisabled();
    });

    test("Unknown", async () => {
      const { default: LabV2 } = await import("@/components/labs/lab-v2");
      const { labInstanceMeta } = mockStandardData(
        {},
        {
          labStatusOverride: {
            status: "timeout",
            creation_progress: null,
          },
        }
      );
      mockedV2GetLabInstance.mockReturnValue(
        Promise.reject(new Error("Unknown Error"))
      );
      const actx = useActx();
      const mockedLocation = vitest.spyOn(window, "location", "get");
      const mockedSetLocation = vitest.fn<(value: string) => void>();

      mockedLocation.mockReturnValue({
        set href(value: string) {
          mockedSetLocation(value);
        },
      } as any);

      const ui = render(<LabV2 instance={labInstanceMeta} />, {
        wrapper: ProvidersWrapper,
      });
      await actx(async () => {});
      expect(
        ui.getByRole("heading", { name: "Lab is not available" })
      ).toBeVisible();
      expect(
        ui.getByRole("button", { name: "Refresh Page" })
      ).not.toBeDisabled();
      expect(ui.queryByRole("button", { name: "Terminate" })).toBeNull();

      ui.getByRole("button", { name: "Refresh Page" }).click();
      expect(mockedSetLocation).toHaveBeenCalledWith("/");
    });
  });

  suite("Functions", () => {
    test("First Module", async () => {
      const { default: LabV2 } = await import("@/components/labs/lab-v2");
      const { labInstanceMeta } = mockStandardData();
      const actx = useActx();

      const ui = render(<LabV2 instance={labInstanceMeta} />, {
        wrapper: ProvidersWrapper,
      });
      await actx(async () => {});

      expect(ui.container.querySelector("article")).toHaveTextContent(
        "guide content"
      );

      const nextButton = ui.queryByRole("button", { name: "Next" })!;
      const previousButton = ui.queryByRole("button", { name: "Previous" })!;

      expect(nextButton).not.toBeDisabled();
      expect(previousButton).toBeDisabled();

      await actx(() => {
        nextButton.click();
      });

      expect(mockedAppRouterInstance.push).toHaveBeenCalledWith(
        "/lab?instance=1001&step=1"
      );
    });

    test("Middle Module", async () => {
      const { default: LabV2 } = await import("@/components/labs/lab-v2");
      const { labInstanceMeta } = mockStandardData({ step: "1" });
      const actx = useActx();

      const ui = render(<LabV2 instance={labInstanceMeta} />, {
        wrapper: ProvidersWrapper,
      });
      await actx(async () => {});

      expect(ui.container.querySelector("article")).toHaveTextContent(
        "module 1 content"
      );

      const nextButton = ui.queryByRole("button", { name: "Next" })!;
      const previousButton = ui.queryByRole("button", { name: "Previous" })!;

      expect(nextButton).not.toBeDisabled();
      expect(previousButton).not.toBeDisabled();

      await actx(() => {
        nextButton.click();
      });

      expect(mockedAppRouterInstance.push).toHaveBeenCalledWith(
        "/lab?instance=1001&step=2"
      );

      await actx(() => {
        previousButton.click();
      });

      expect(mockedAppRouterInstance.push).toHaveBeenCalledWith(
        "/lab?instance=1001"
      );
    });

    test("Last Module", async () => {
      const { default: LabV2 } = await import("@/components/labs/lab-v2");
      const { labInstanceMeta } = mockStandardData({ step: "2" });
      const actx = useActx();

      const ui = render(<LabV2 instance={labInstanceMeta} />, {
        wrapper: ProvidersWrapper,
      });
      await actx(async () => {});

      expect(ui.container.querySelector("article")).toHaveTextContent(
        "module 2 content"
      );

      const nextButton = ui.queryByRole("button", { name: "Next" })!;
      const previousButton = ui.queryByRole("button", { name: "Previous" })!;

      expect(nextButton).toBeDisabled();
      expect(previousButton).not.toBeDisabled();

      await actx(() => {
        previousButton.click();
      });

      expect(mockedAppRouterInstance.push).toHaveBeenCalledWith(
        "/lab?instance=1001&step=1"
      );
    });

    test("Resources Button", async () => {
      const { default: LabV2 } = await import("@/components/labs/lab-v2");
      const { labInstanceMeta } = mockStandardData();
      const actx = useActx();

      const ui = render(<LabV2 instance={labInstanceMeta} />, {
        wrapper: ProvidersWrapper,
      });
      await actx(async () => {});

      const resourcesButton = ui.queryByText("Resources")! as HTMLButtonElement;
      expect(resourcesButton).not.toBeDisabled();
      await actx(() => resourcesButton.click());
      expect(ui.queryByRole("dialog", { name: "Lab Resources" })).toBeVisible();
    });

    test("Web SSH Button", async () => {
      const { default: LabV2 } = await import("@/components/labs/lab-v2");
      const { labInstanceMeta } = mockStandardData();
      const actx = useActx();

      const ui = render(<LabV2 instance={labInstanceMeta} />, {
        wrapper: ProvidersWrapper,
      });
      await actx(async () => {});

      const webSshButton = ui.queryByText("Web SSH")! as HTMLButtonElement;
      expect(webSshButton).not.toBeDisabled();
      await actx(() => webSshButton.click());
      await actx(() => {});

      expect(ui.queryByRole("dialog", { name: "test-ssh1 1" })).toBeVisible();
    });

    test("Terminate Button", async () => {
      const { default: LabV2 } = await import("@/components/labs/lab-v2");
      const { labInstanceMeta } = mockStandardData();
      const actx = useActx();

      const ui = render(<LabV2 instance={labInstanceMeta} />, {
        wrapper: ProvidersWrapper,
      });
      await actx(async () => {});

      const terminateBytton = ui.queryByText("Terminate")! as HTMLButtonElement;
      expect(terminateBytton).not.toBeDisabled();

      await actx(() => terminateBytton.click());
      await actx(() => {});

      expect(
        ui.queryByRole("alertdialog", {
          name: "Are you sure you want to terminate this lab?",
        })
      ).toBeVisible();

      await actx(async () => {
        ui.queryByText("Confirm")!.click();
      });

      expect(mockedV2GetLabInstance).toHaveBeenCalledWith(1001);
    });
  });
});
