import { Page, TestInfo } from 'playwright/test';
import { expect, TestExtended } from '../fixtures';
import { ButtonFindOptions } from '../widgets';
import { AutoBind, describedStep } from '../utils';
import excel, { Workbook, Worksheet } from 'exceljs';
import path from 'path';
import { existsSync } from 'fs';
import { EntityGeneratorApi } from '../api/types';

type Context = {
  page: Page;
  widgetHelper: TestExtended['widgetHelper'];
  actions: TestExtended['actions'];
  testInfo: TestInfo;
};

type NavigateParams =
  | {
      module: string;
      reportName: string;
    }
  | (() => Promise<void>);

type MakeParams = {
  context: Context;
  navigateParams: NavigateParams;
  runReportBtn?: ButtonFindOptions;
  user: { username: string; password?: string };
  fillReportFormFunc?: () => Promise<void>;
  generateTestData?: () => Promise<EntityGeneratorApi>;
  verifyExcelFileData?: (
    generatorApi: EntityGeneratorApi,
    excelApi: ExcelApiClass
  ) => Promise<any> | any;
  excelFileExt?: string;
};

export class ReportScenario {
  private DEFAULT_RUN_REPORT_BUTTON_FIND_PARAMS = {
    label: 'Spustit generování dokumentu',
  };
  private DEFAULT_EXCEL_FILE_EXT = 'xlsx';

  protected generatorApi: EntityGeneratorApi = {
    data: {},
    indexed: {},
    get() {
      throw new Error('NOT implemented');
    },
    getById() {
      throw new Error('NOT implemented');
    },
  };

  constructor(protected readonly params: MakeParams) {}

  static async makeAndRun(params: MakeParams) {
    const { fillReportFormFunc, generateTestData, verifyExcelFileData } =
      params;
    const instance = new ReportScenario(params);

    if (generateTestData) await instance?.generateTestData();
    await instance.navigate();
    if (fillReportFormFunc) await instance.fillReportForm();
    await instance.runReport();
    if (verifyExcelFileData) await instance.verifyExcelFileData();
  }

  /**
   * @description This function designed for KB reports only for now, use custom navigate function if you need
   */
  @describedStep('Přejít na formulář reportu')
  private async navigate() {
    if (typeof this.params.navigateParams === 'function') {
      return this.params.navigateParams();
    }
    await this.page.goto('/');
    await this.ctx.actions.loginAsUser(this.user.username, this.user.password);
    const { w } = this;
    const { module, reportName } = this.params.navigateParams;
    await w.tile({ label: 'Administrace' }).click();
    await w.tile({ label: 'Reporty' }).click();
    await w.collapsible({ label: `Modul: ${module}` }).toggle();
    await w.table().body.child.text({ text: reportName }).click();
    await w.header({ text: reportName }).isVisible();
  }

  @describedStep('Vytvoř testovacíh data')
  private async generateTestData() {
    const api = await this.params.generateTestData?.();
    if (api) this.generatorApi = api;
  }

  @describedStep('Vyplnit formulář')
  private async fillReportForm() {
    await this.params.fillReportFormFunc?.();
  }

  @describedStep('Spustit generování dokumentu')
  private async runReport() {
    await this.w
      .button(
        this.params.runReportBtn || this.DEFAULT_RUN_REPORT_BUTTON_FIND_PARAMS
      )
      .click();
  }

  @describedStep('Ověřit obsah souboru')
  private async verifyExcelFileData() {
    const filePath = await this.downloadReportAndAttachFile();
    const workbook = new excel.Workbook();
    await workbook.xlsx.readFile(filePath);

    await this.params.verifyExcelFileData?.(
      this.generatorApi,
      new ExcelApiClass(workbook)
    );
  }

  @describedStep('Sáhnout report a připojit soubor ke zprávě')
  private async downloadReportAndAttachFile() {
    const downloadPromise = this.page.waitForEvent('download');
    const fileText = this.w.text({
      text: `.${this.excelFileExt}`,
      exact: false,
    });
    const fileName = await fileText.l.innerText();
    await fileText.click();
    const download = await downloadPromise;

    const filePath = path.join('./downloads', fileName);
    await download.saveAs(filePath);

    if (!existsSync(filePath))
      throw new Error(`File ${filePath} does not exist`);

    this.params.context.testInfo.attach(fileName, {
      path: filePath,
      contentType:
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    });

    return filePath;
  }

  get ctx() {
    return this.params.context;
  }
  get user() {
    return this.params.user;
  }
  get page() {
    return this.ctx.page;
  }

  get w() {
    return this.ctx.widgetHelper;
  }

  get excelFileExt() {
    return this.params.excelFileExt || this.DEFAULT_EXCEL_FILE_EXT;
  }
}

class ExcelApiClass extends AutoBind {
  constructor(public readonly workbook: Workbook) {
    super();
  }

  @describedStep('Řádek existuje', { withArgs: true })
  rowExists(sheet: number, row: number) {
    const worksheet = this.workbook.getWorksheet(sheet);
    expect(worksheet).toBeTruthy();
    expect(worksheet!.getRow(row)).toBeTruthy();
  }

  @describedStep('Bunka obsahuje text', { withArgs: true })
  cellContain(
    sheet: number,
    row: number,
    cell: number,
    content: string | null | undefined
  ) {
    const worksheet = this.workbook.getWorksheet(sheet);
    expect(worksheet).toBeTruthy();
    expect(worksheet!.getRow(row).getCell(cell).text).toBe(content);
  }
}
