import {
  Page,
  test as base,
  chromium,
  BrowserContext,
  errors,
} from "@playwright/test";
import { injectLocatorHighlightScripts } from "@empiricalrun/playwright-utils/test";
import path from "path";
import * as fs from "fs";
import { rimrafSync } from "rimraf";
import { TEST_PASSWORD } from "./test-data";
import {
  UserContextForLedger,
  UserContextForPrivateKey,
  UserContextForSeedPhrase,
} from "./test-data/types";
import { LedgerEmulator } from "./ledger/emulator";

const videoStore = "videos-store";
export const authFilesRootDir = "auth";
export const authFileForUser = (user) =>
  path.join(authFilesRootDir, `${user.fileNameInCache}.json`);

export type TestOptions = {
  context: BrowserContext;
  userDataDir: string;
  extensionId: string;
  extensionPage: Page;
  saveVideos: void;
  userContext?:
  | UserContextForPrivateKey
  | UserContextForSeedPhrase
  | UserContextForLedger;
  loggedInPage: Page;
  ledger: LedgerEmulator | undefined;
  appType: "cosmos" | "compass";
};

export const test = base.extend<TestOptions>({
  userDataDir: "",
  context: async ({ userDataDir, viewport }, use, testInfo) => {
    const pathToExtension = path.join(process.cwd(), "extension-src");
    const args = [
      `--disable-extensions-except=${pathToExtension}`,
      `--load-extension=${pathToExtension}`,
    ];
    if (process.env.CI) {
      args.push(`--headless=new`);
    }
    const options = {
      headless: false,
      args,
      viewport,
      permissions: ["clipboard-read", "clipboard-write"],
    };
    const hasRecordVideo = testInfo.config.metadata.video === "on";
    if (hasRecordVideo) {
      options["recordVideo"] = {
        dir: path.join(testInfo.project.outputDir, videoStore, testInfo.testId),
        size: { width: 1366, height: 768 },
      };
    }
    const context = await chromium.launchPersistentContext(
      userDataDir,
      options,
    );
    // Persistent context always launches with an empty page
    const firstPage = context.pages() ? context.pages()[0] : undefined;
    const firstPageVideo = await firstPage?.video()!.path();
    await use(context);
    await context.close();
    if (firstPageVideo && fs.existsSync(firstPageVideo)) {
      fs.rmSync(firstPageVideo);
    }
  },
  extensionId: async ({ context }, use) => {
    let [background] = context.serviceWorkers();
    if (!background) {
      background = await context.waitForEvent("serviceworker");
    }
    const extensionId = background.url().split("/")[2];
    await use(extensionId);
  },
  extensionPage: async ({ context, extensionId }, use) => {
    let extPage: Page;
    try {
      // Chrome extension is opening a tab on first load for onboarding
      extPage = await context.waitForEvent("page", {
        predicate: (page) =>
          page.url().includes(`chrome-extension://${extensionId}`),
        timeout: 10_000,
      });
    } catch (err) {
      // If that does not happen, we open a new tab
      if (err instanceof errors.TimeoutError) {
        extPage = await context.newPage();
        await extPage.goto(`chrome-extension://${extensionId}/index.html`);
      } else {
        throw err;
      }
    }
    //@ts-ignore
    injectLocatorHighlightScripts(extPage);
    await use(extPage);
  },
  saveVideos: [
    async ({ }, use, testInfo) => {
      await use();
      const pathToTestVideos = path.join(
        testInfo.project.outputDir,
        videoStore,
        testInfo.testId,
      );
      if (fs.existsSync(pathToTestVideos)) {
        for (let name of fs.readdirSync(pathToTestVideos)) {
          await testInfo.attach("video", {
            path: path.join(pathToTestVideos, name),
            contentType: "video/webm",
          });
        }
        rimrafSync(pathToTestVideos);
      }
    },
    { auto: true },
  ],
  userContext: [undefined, { option: true }],
  appType: ["cosmos", { option: true }], // Cosmos is default
  loggedInPage: async ({ userContext, extensionPage, appType }, use) => {
    if (!userContext) {
      throw new Error(`userContext is required for loggedInPage fixture.`);
    }
    const context = extensionPage.context();
    let [background] = context.serviceWorkers();
    if (!background) {
      background = await context.waitForEvent("serviceworker");
    }
    const authFile = authFileForUser(userContext);
    const contents = JSON.parse(
      fs.readFileSync(authFile, { encoding: "utf-8" }),
    );
    await background.evaluate((contents) => {
      return new Promise((resolve) => {
        // @ts-ignore
        // eslint-disable-next-line no-undef
        chrome.storage.local.set(contents, () => {
          resolve("done");
        });
      });
    }, contents);

    await extensionPage.reload();
    await extensionPage
      .getByPlaceholder("Enter password", { exact: true })
      .fill(TEST_PASSWORD);
    // The above can fail (due to unknown reasons), and so we retry it
    await extensionPage
      .getByPlaceholder("Enter password", { exact: true })
      .fill(TEST_PASSWORD);
    await extensionPage.getByText("Unlock wallet", { exact: true }).click();

    if (appType === "compass") {
      // Feature drawer to be dismissed
      const drawer = extensionPage.getByRole("heading", {
        name: "Link addresses to explore Sei",
      });
      await extensionPage.waitForTimeout(5_000);
      if (await drawer.isVisible()) {
        await extensionPage.getByRole("button", { name: "Cross" }).click();
      }
    }

    await use(extensionPage);
  },
  ledger: async ({ userContext, extensionPage }, use, testInfo) => {
    if (!userContext?.fileNameInCache.includes("ledger")) {
      await use(undefined);
      return;
    }
    console.log(
      `setting up ledger emulator for ${testInfo.title} (retry: ${testInfo.retry})`,
    );
    const context = extensionPage.context();
    await context.addInitScript(() => {
      //@ts-ignore
      window.TRANSPORT_API_PORT = 5000;
    });
    const ledger = new LedgerEmulator();
    await ledger.start("cosmos");
    // TODO: move the page to be inside the ledger, so that
    // ethereum transition is captured by the video recording
    const ledgerPage = await context.newPage();
    await extensionPage.reload();
    await ledgerPage.goto(ledger.url()!);
    await use(ledger);
    await ledger.stop();
  },
});

export const expect = test.expect;
