import {
  describe,
  it,
  vi,
  beforeAll,
  beforeEach,
  afterEach,
  afterAll,
} from 'vitest';
import type {
  eventWithTime,
  listenerHandler,
  mutationData,
} from '@rrweb/types';
import { unpack } from '@rrweb/packer';
import * as fs from 'fs';
import * as path from 'path';
import type * as puppeteer from 'puppeteer';
import type { recordOptions } from 'rrweb';
import type {} from '@rrweb/types';
import { EventType } from '@rrweb/types';
import {
  assertSnapshot,
  getServerURL,
  launchPuppeteer,
  startServer,
  waitForRAF,
} from './utils';
import type * as http from 'http';

interface ISuite {
  code: string;
  browser: puppeteer.Browser;
  page: puppeteer.Page;
  events: eventWithTime[];
  server: http.Server;
  serverURL: string;
}

interface IWindow extends Window {
  rrweb: {
    record: (
      options: recordOptions<eventWithTime>,
    ) => listenerHandler | undefined;
    addCustomEvent<T>(tag: string, payload: T): void;
    pack: (e: eventWithTime) => string;
  };
  emit: (e: eventWithTime) => undefined;
  snapshots: eventWithTime[];
}
type ExtraOptions = {
  usePackFn?: boolean;
};

async function injectRecordScript(
  frame: puppeteer.Frame,
  options?: ExtraOptions,
) {
  await frame.addScriptTag({
    path: path.resolve(__dirname, '../dist/all.umd.cjs'),
  });
  options = options || {};
  await frame.evaluate((options) => {
    (window as unknown as IWindow).snapshots = [];
    const { record, pack } = (window as unknown as IWindow).rrweb;
    const config: recordOptions<eventWithTime> = {
      recordCrossOriginIframes: true,
      recordCanvas: true,
      emit(event) {
        (window as unknown as IWindow).snapshots.push(event);
        (window as unknown as IWindow).emit(event);
      },
    };
    if (options.usePackFn) {
      config.packFn = pack;
    }
    record(config);
  }, options);

  for (const child of frame.childFrames()) {
    await injectRecordScript(child, options);
  }
}

const setup = function (
  this: ISuite,
  content: string,
  options?: ExtraOptions,
): ISuite {
  const ctx = {} as ISuite;

  beforeAll(async () => {
    ctx.browser = await launchPuppeteer();
    ctx.server = await startServer();
    ctx.serverURL = getServerURL(ctx.server);
  });

  beforeEach(async () => {
    ctx.page = await ctx.browser.newPage();
    await ctx.page.goto('about:blank');
    await ctx.page.setContent(
      content.replace(/\{SERVER_URL\}/g, ctx.serverURL),
    );
    ctx.events = [];
    await ctx.page.exposeFunction('emit', (e: eventWithTime) => {
      if (e.type === EventType.DomContentLoaded || e.type === EventType.Load) {
        return;
      }
      ctx.events.push(e);
    });

    ctx.page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
    await injectRecordScript(ctx.page.mainFrame(), options);
  });

  afterEach(async () => {
    await ctx.page.close();
  });

  afterAll(async () => {
    await ctx.browser.close();
    ctx.server.close();
  });

  return ctx;
};

describe('cross origin iframes & packer', function (this: ISuite) {
  vi.setConfig({ testTimeout: 100_000 });

  describe('blank.html', function (this: ISuite) {
    const content = `
    <!DOCTYPE html>
    <html>
      <body>
        <iframe src="{SERVER_URL}/html/blank.html"></iframe>
      </body>
    </html>
  `;
    const ctx = setup.call(this, content, { usePackFn: true });

    describe('should support packFn option in record()', () => {
      it('', async () => {
        const frame = ctx.page.mainFrame().childFrames()[0];
        await waitForRAF(frame);
        const packedSnapshots = (await ctx.page.evaluate(
          'window.snapshots',
        )) as string[];
        const unpackedSnapshots = packedSnapshots.map((packed) =>
          unpack(packed),
        ) as eventWithTime[];
        await assertSnapshot(unpackedSnapshots);
      });
    });
  });
});
