import fs from 'fs';
import {
  EvaluateFunc,
  EventEmitter,
  KeyInput,
  PageEventObject,
  Protocol,
  WaitForOptions,
} from 'puppeteer';
import path from 'path';

import { BrowserInterface, LooseObject } from '../../../src/typings';

import { APSRequestHandler } from './aps-request-handler';
import { GetMonthlyUtilityDataRequestHandlerImpl } from './index';
import { MockResponse } from './mock-response';
import { MockTarget } from './mock-target';

const workFlowConfigFilePath = path.join(
  'src',
  'services',
  'workflow',
  'workflow.json',
);
const apsWorkflowJson = JSON.parse(
  fs.readFileSync(workFlowConfigFilePath).toString(),
);

const mockWorkflowPath = path.join(
  'test',
  'mocks',
  'mock-data-extraction',
  'mock-workflow-url.json',
);
const mockWorkflowJSON = JSON.parse(
  fs.readFileSync(mockWorkflowPath).toString(),
);

export class MockPage implements BrowserInterface.Page {
  password: string;
  username: string;
  requestHandler: LooseObject;
  requestHandlerFlow: LooseObject = new Map();
  constructor() {
    this.requestHandlerFlow.set(
      mockWorkflowJSON.loginUrlAPS,
      new APSRequestHandler(),
    );
    this.requestHandlerFlow.set(
      mockWorkflowJSON.billDetailsPageAPS,
      new GetMonthlyUtilityDataRequestHandlerImpl(),
    );
  }

  keyboard: BrowserInterface.Keyboard = {
    press: async (key: KeyInput) => {
      return Promise.resolve();
    },
  };

  async type(selector: string, text: string) {
    this.requestHandler.type(selector, text);
    return Promise.resolve();
  }

  async goto(
    url: string,
    options?: WaitForOptions & {
      referer?: string;
    },
  ) {
    const requestHandler = this.requestHandlerFactory(url);
    if (!requestHandler) {
      return;
    }
    this.requestHandler = this.requestHandlerFactory(url);

    const mockResponse: BrowserInterface.HTTPResponse = new MockResponse();
    return Promise.resolve(mockResponse);
  }

  async waitForResponse(
    urlOrPredicate:
      | string
      | ((res: BrowserInterface.HTTPResponse) => boolean | Promise<boolean>),
    options?: {
      timeout?: number;
    },
  ) {
    const data = await this.requestHandler.waitForResponse(
      urlOrPredicate,
      options,
    );
    const mockResponse = new MockResponse();
    mockResponse.data = data;
    return mockResponse;
  }

  async waitForSelector(
    selector: string,
    options?: {
      visible?: boolean;
      hidden?: boolean;
      timeout?: number;
    },
  ) {
    try {
      if (selector === 'span.aps-btn-link') {
        return Promise.resolve(null);
      }
      if (selector === apsWorkflowJson.getServiceAccounts.addressDataSelector) {
        return Promise.resolve(null);
      }
    } catch (err) {
      return Promise.reject(err);
    }

    this.requestHandler.waitForSelector(selector, options);
    return Promise.resolve(null);
  }

  async waitForNavigation(options?: {
    timeout: number;
    waitUntil: 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2';
  }) {
    const mockResponse = new MockResponse();
    return mockResponse;
  }

  async $eval<ReturnType>(
    selector: string,
    pageFunction: (
      element: Element,
      ...args: unknown[]
    ) => ReturnType | Promise<ReturnType>,
  ) {
    const data = this.requestHandler.$eval(selector, pageFunction);
    return Promise.resolve(data);
  }

  async select(selector: string, ...values: string[]) {
    const data = this.requestHandler.select(selector, values);
    return Promise.resolve(data);
  }
  // tslint:disable-next-line: function-name
  async $$(selector: string) {
    const data = this.requestHandler.$$(selector);
    return Promise.resolve(data);
  }

  // tslint:disable-next-line: function-name
  async $$eval<ReturnType>(
    selector: string,
    pageFunction: (
      elements: Element[],
      ...args: unknown[]
    ) => ReturnType | Promise<ReturnType>,
  ) {
    const data = this.requestHandler.$$eval(selector, pageFunction);
    return Promise.resolve(data);
  }

  async evaluate<
    Params extends unknown[],
    Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
  >(pageFunction: Func | string, ...args: Params) {
    const data = this.requestHandler.evaluate(pageFunction, args);
    return data;
  }

  target() {
    const mockTarget = new MockTarget();
    return mockTarget;
  }

  requestHandlerFactory(url: string) {
    return this.requestHandlerFlow.get(url);
  }

  setDefaultTimeout(timeout: number) {
    return Promise.resolve();
  }

  async setRequestInterception(value: Boolean) {
    return Promise.resolve();
  }

  on<K extends keyof PageEventObject>(
    eventName: K,
    handler: (event: PageEventObject[K]) => void,
  ): EventEmitter {
    return;
  }

  async setCookie(...cookies: Protocol.Network.CookieParam[]): Promise<void> {
    return Promise.resolve();
  }
}
