import { expect, Locator, Page } from '@playwright/test';
import { readFileSync } from 'fs';

type ResponseSelector = (json: any) => boolean;

export default abstract class BasePage {
  readonly rootPage: Page;

  abstract get(args?: any): Locator;

  constructor(rootPage: Page) {
    this.rootPage = rootPage;
  }

  async verifyToast({ message }: { message: string }) {
    await this.rootPage.locator('.ant-message .ant-message-notice-content', { hasText: message }).last().isVisible();

    // ensure that the toast is removed from the DOM
    // await this.rootPage.waitForSelector('.ant-message .ant-message-notice-content', { state: 'hidden' });
  }

  async verifyErrorMessage({ message }: { message: RegExp }) {
    await expect(this.get().locator('.ant-form-item-explain-error', { hasText: message })).toBeVisible();
  }

  async waitForResponse({
    // Playwright action that triggers the request i.e locatorSomething.click()
    uiAction,
    httpMethodsToMatch = [],
    requestUrlPathToMatch,
    // A function that takes the response body and returns true if the response is the one we are looking for
    responseJsonMatcher,
    timeout,
    responseStatusCodeToMatch = 200,
  }: {
    uiAction: () => Promise<any>;
    requestUrlPathToMatch: string | RegExp;
    httpMethodsToMatch?: string[];
    responseJsonMatcher?: ResponseSelector;
    timeout?: number;
    responseStatusCodeToMatch?: number;
  }) {
    const [res] = await Promise.all([
      this.rootPage.waitForResponse(
        res =>
          (requestUrlPathToMatch instanceof RegExp
            ? requestUrlPathToMatch.test(res.url())
            : res.url().includes(requestUrlPathToMatch)) &&
          res.status() === responseStatusCodeToMatch &&
          httpMethodsToMatch.includes(res.request().method()),
        timeout ? { timeout } : undefined
      ),
      uiAction(),
    ]);

    // handle JSON matcher if provided
    let isResJsonMatched = true;
    if (responseJsonMatcher) {
      try {
        isResJsonMatched = responseJsonMatcher(await res.json());
      } catch {
        isResJsonMatched = false;
      }
    }
    return isResJsonMatched;
  }

  async attachFile({ filePickUIAction, filePath }: { filePickUIAction: Promise<any>; filePath: string[] }) {
    const [fileChooser] = await Promise.all([
      // It is important to call waitForEvent before click to set up waiting.
      this.rootPage.waitForEvent('filechooser'),
      // Opens the file chooser.
      filePickUIAction,
    ]);
    await fileChooser.setFiles(filePath);
  }

  async downloadAndGetFile({ downloadUIAction }: { downloadUIAction: Promise<any> }) {
    const [download] = await Promise.all([
      // It is important to call waitForEvent before click to set up waiting.
      this.rootPage.waitForEvent('download'),
      // Triggers the download.
      downloadUIAction,
    ]);
    // wait for download to complete
    if (await download.failure()) {
      throw new Error('Download failed');
    }

    const file = await download.createReadStream();
    const data = await new Promise((resolve, reject) => {
      let data = '';
      file?.on('data', chunk => (data += chunk));
      file?.on('end', () => resolve(data));
      file?.on('error', reject);
    });
    return data as any;
  }

  async getClipboardText() {
    return await this.rootPage.evaluate(() => navigator.clipboard.readText());
  }

  async copyToClipboard({ text }: { text: string }) {
    await this.rootPage.evaluate(text => navigator.clipboard.writeText(text), text);
  }

  async os() {
    return await this.rootPage.evaluate(() => navigator.platform);
  }

  async isMacOs() {
    return (await this.os()).includes('Mac');
  }

  async dropFile({ imageFilePath, domSelector }: { imageFilePath?: string; domSelector: string }) {
    const buffer = readFileSync(imageFilePath).toString('base64');

    const dataTransfer = await this.rootPage.evaluateHandle(
      async ({ bufferData, localFileName, localFileType }) => {
        const dt = new DataTransfer();

        const blobData = await fetch(bufferData).then(res => res.blob());

        const file = new File([blobData], localFileName, { type: localFileType });
        dt.items.add(file);
        return dt;
      },
      {
        bufferData: `data:application/octet-stream;base64,${buffer}`,
        localFileName: 'test.png',
        localFileType: 'image/png',
      }
    );

    await this.rootPage.dispatchEvent(domSelector, 'drop', { dataTransfer });
  }

  // async copyImageToClipboard({ imageFilePath, domSelector }: { imageFilePath?: string; domSelector: string }) {
  //   const pasteEvent = await this.rootPage.evaluate(async () => {
  //     const base64 = `data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA
  //     AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
  //         9TXL0Y4OHwAAAABJRU5ErkJggg==`;

  //     const response = await fetch(base64);
  //     const blob = await response.blob();

  //     const clipboardData = new DataTransfer();
  //     clipboardData.items.add(new File([blob], 'foo.png', { type: blob.type }));
  //     let pasteEvent = new Event('paste', { bubbles: true, cancelable: true });
  //     pasteEvent = Object.assign(pasteEvent, {
  //       clipboardData,
  //     });

  //     return pasteEvent;
  //   });

  //   await this.rootPage.dispatchEvent(domSelector, 'paste', { pasteEvent });
  // }
}
