import type * as Playwright from "playwright";
import { chromium, devices } from "playwright";
import type { Database } from "./database";
import type { TPlatform } from "./platform";

export type PageOrFrame = Playwright.Page | Playwright.Frame;

export type ImageFile = {
  url: string;
  blocks?: Array<Record<string, number>>;
  width?: number;
  height?: number;
};

async function drawImage(imageFile: ImageFile): Promise<string> {
  const canvas = Object.assign(document.createElement("canvas"), {
    width: imageFile.width,
    height: imageFile.height,
  });

  const image = (await new Promise((resolve) => {
    Object.assign(new Image(), {
      crossOrigin: "use-credentials",
      src: imageFile.url,
      onload() {
        resolve(this);
      },
    });
  })) as HTMLImageElement;

  const ctx = canvas.getContext("2d")!;

  for (const q of imageFile.blocks!) {
    ctx.drawImage(
      image,
      q.destX,
      q.destY,
      q.width,
      q.height,
      q.srcX,
      q.srcY,
      q.width,
      q.height,
    );
  }

  const dataUrl = canvas.toDataURL();
  return dataUrl;
}

async function fetchImage(imageFile: ImageFile): Promise<string> {
  const res = await fetch(imageFile.url);
  const blob = await res.blob();
  const dataUrl: string = await new Promise((resolve, reject) => {
    const fileReader = Object.assign(new FileReader(), {
      onload(): void {
        resolve(this.result);
      },
      onerror(e: ErrorEvent): void {
        const error = new Error(`${e.type}: ${e.message}`);
        reject(error);
      },
    });

    fileReader.readAsDataURL(blob);
  });

  return dataUrl;
}

async function dataUrlToBlob(dataUrl: string): Promise<Blob> {
  const res = await fetch(dataUrl);
  return await res.blob();
}

export type Browser = {
  loadBrowserContext(platform: TPlatform): Promise<Playwright.BrowserContext>;
  saveBrowserContext(platform: TPlatform, ctx: BrowserContext): Promise<void>;
  newContext(): Promise<Playwright.BrowserContext>;
  close(): Promise<void>;
  drawImage(
    pageOrFrame: Playwright.Page | Playwright.Frame,
    imageFile: ImageFile,
  ): Promise<Blob>;
};

export type BrowserContext = Playwright.BrowserContext;

export async function createBrowser({
  db,
  headless = true,
}: {
  db: Database;
  headless?: boolean;
}): Promise<Browser> {
  const { userAgent } = devices["Desktop Chrome"];
  const browser = await chromium.launch({
    headless,
    args: ["--disable-blink-features=AutomationControlled"],
  });

  return {
    async loadBrowserContext(
      platform: TPlatform,
    ): Promise<Playwright.BrowserContext> {
      const { secrets } = await db.get(
        `select secrets from platforms where name = ?`,
        platform,
      );

      const storageState = JSON.parse(secrets) ?? undefined;
      const ctx = await browser.newContext({ storageState, userAgent });
      return ctx;
    },

    async saveBrowserContext(
      platform: TPlatform,
      ctx: BrowserContext,
    ): Promise<void> {
      const secrets = await ctx.storageState();
      await db.run(
        `update platforms set secrets = ? where name = ?`,
        JSON.stringify(secrets),
        platform,
      );
    },

    newContext: () => browser.newContext(),
    close: () => browser.close(),

    async drawImage(
      pageOrFrame: Playwright.Page | Playwright.Frame,
      imageFile: ImageFile,
    ): Promise<Blob> {
      if (Array.isArray(imageFile.blocks) && imageFile.blocks.length > 0) {
        const dataUrl = await pageOrFrame.evaluate(drawImage, imageFile);
        return await dataUrlToBlob(dataUrl);
      }

      if (imageFile.url.startsWith("blob:")) {
        const dataUrl = await pageOrFrame.evaluate(fetchImage, imageFile);
        return await dataUrlToBlob(dataUrl);
      }

      const page = "page" in pageOrFrame ? pageOrFrame.page() : pageOrFrame;
      const res = await page.context().request.get(imageFile.url);
      const headers = res.headers();
      const buffer = await res.body();

      let type = headers["content-type"];

      if (type === "binary/octet-stream") {
        const [, extension] =
          /^attachment *; *filename="[^"]+[.]([^.]+)" *(?:$|;)/i.exec(
            headers["content-disposition"],
          ) ?? [];

        switch (extension) {
          case "zip":
            type = "application/zip";
            break;
        }
      }

      return new Blob([buffer], { type });
    },
  };
}