import { Component, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild, ElementRef, ViewEncapsulation, HostListener, signal, inject, AfterViewInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { tap, catchError, switchMap, shareReplay, retry, map, filter } from 'rxjs/operators';
import { of, BehaviorSubject, Subject } from 'rxjs';
import { State } from '@progress/kendo-data-query';
import { MessageService, promptAction, PromptSettings } from '../_shared/services/message.service';
import { CustomFormBuilder } from '../_shared/services/custom-form-builder.service';
import * as util from '../_shared/utils/util';
import { BcStatusResponse, InvoiceGasService, InvoiceSelection, RequiredData } from './invoice-gas.service';
import { FAST_KENDO_COMMON, FAST_PAGE_COMMON } from '../app.config';
import { Validators } from '@angular/forms';
import dayjs from 'dayjs';
import { InvoiceGasDetailComponent } from "../invoice-gas-detail/invoice-gas-detail.component";
import { GridDataResult, RowArgs, SelectableSettings } from '@progress/kendo-angular-grid';
import { InvoiceGasGridItem } from '../invoice-gas/models/InvoiceGasGridItem';
import { environment } from '../../environments/environment';
import { InvoiceDistributeComponent } from "../invoice-distribute/invoice-distribute.component";
import { FastGridComponent } from '../_shared/elements/fast-grid.component';
import { ToastService } from '../_shared/services/fast-toast.service';
import { AuthClass } from '../_shared/utils/authClass';
import { FastTipDirective } from '../_shared/elements/fast-tip.directive';
import { HasDifferencesPipe } from '../_shared/pipes/has-differences.pipe';

interface InvoiceNumAndFileName {
  invoiceNum: string;
  fileName: string;
}

@Component({
  selector: 'app-invoice-gas',
  standalone: true,
  imports: [FAST_KENDO_COMMON, FAST_PAGE_COMMON, InvoiceGasDetailComponent, InvoiceDistributeComponent, HasDifferencesPipe],
  templateUrl: './invoice-gas.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class InvoiceGasComponent implements AfterViewInit {
  @ViewChild("grid", { read: ElementRef }) fastGridEl: ElementRef;
  @ViewChild('grid') grid: FastGridComponent;
  @HostListener('window:resize') onResize() {
    //this function is empty but for some reason it helps the window to resize faster
  };

  service = inject(InvoiceGasService);
  messageService = inject(MessageService);
  titleService = inject(Title);
  fb = inject(CustomFormBuilder);
  ref = inject(ChangeDetectorRef);
  toast = inject(ToastService);

  constructor() {

  }

  ngAfterViewInit() {
    this.refreshItems$.next();
  }

  util = util;
  localRequiredData: RequiredData;
  monthSelectionsForm = this.getMonthSelectionsForm();
  gridScrollPosition: util.GridScrollPosition = { topPos: 0, leftPos: 0 };
  mySelection: number[] = [];
  gridData: InvoiceGasGridItem[] = [];
  isAnyRowSelected = signal(false);
  invoicesSelection: InvoiceSelection[] = [];
  gridLoading = signal(true);
  refreshItems$ = new Subject<void>();
  exporting$ = new BehaviorSubject<boolean>(false);
  exportClicked$ = new Subject();
  downloadInvoice$ = new Subject<InvoiceNumAndFileName>();
  approveLines$ = new Subject<null>();
  detailOpened$ = new BehaviorSubject<boolean>(false);
  exportToBusinessCentral$ = new Subject();
  bcExporting$ = new BehaviorSubject<boolean>(false);
  bcStatus$ = new Subject<string>();
  bcExportStatus = signal<'Success' | 'Error' | 'None'>('None');
  currentStatus = signal<BcStatusResponse>(null);
  detailId = 0;
  internalEntityId = 0;
  counterpartyId = 0;
  month = '';
  distributeOpened$ = new BehaviorSubject<boolean>(false);
  isMergingFiles$ = new BehaviorSubject<boolean>(false)
  mergeFiles$ = new Subject<number>();
  lastSelectedIndex: number = null;

  state: State = {
    filter: null,
    group: null,
    skip: 0,
    sort: [
      { field: 'Month', dir: 'asc' },
      { field: 'InternalEntity', dir: 'asc' },
      { field: 'Counterparty', dir: 'asc' },
    ],
    take: environment.production ? 50 : 25
  };

  selectableSettings: SelectableSettings = {
    checkboxOnly: true,
    mode: 'multiple',
    enabled: true
  }

  mySelectionKey(context: RowArgs) {
    const gridItem = (context.dataItem as InvoiceGasGridItem);
    return gridItem?.Id ?? 0;
  }

  getMonthSelectionsForm() {
    const fb = this.fb;

    const isLocalMode = AuthClass.ServerSettings.isLocalModeEnabled;
    const prodDefaultMonthStart = dayjs().startOf('month').toDate();
    const devDefaultMonthStart = dayjs().set('month', 11).set('year', 2025).startOf('month').toDate();
    const defaultMonthStart = environment.production || isLocalMode ? prodDefaultMonthStart : devDefaultMonthStart;
    const defaultMonthEnd = dayjs(defaultMonthStart).endOf('month').toDate();

    const fg: util.FormModel<{ monthStart: Date, monthEnd: Date }> = fb.group({
      monthStart: fb.ctr(defaultMonthStart, Validators.required),
      monthEnd: fb.ctr(defaultMonthEnd, Validators.required)
    });

    return fg;
  }

  onFileIconClick(item: InvoiceGasGridItem, e: Event) {
    this.downloadInvoice(item.InvoiceNum, item.FileNameOriginal);
    e.stopPropagation();
  }

  openDetail(ID: number, internalEntityId: number, counterpartyId: number, month: string): void {
    this.detailId = ID;
    this.counterpartyId = counterpartyId;
    this.internalEntityId = internalEntityId;
    this.month = month;
    this.detailOpened$.next(true);
  }

  closeDetail() {
    this.detailOpened$.next(false);
    this.refreshItems$.next(null);
  }

  openDistribute() {
    this.distributeOpened$.next(true);
  }

  closeDistribute() {
    this.distributeOpened$.next(false);
    this.refreshItems$.next(null);
  }

  edit(dataItem: InvoiceGasGridItem): void {
    // Select the clicked row if not already selected
    if (!this.mySelection.includes(dataItem.Id)) {
      this.mySelection = [dataItem.Id];
    }
    this.onSelectionsChanged();
    this.openDetail(dataItem.Id, dataItem.InternalEntityId, dataItem.CounterpartyId, dataItem.Month);
  }

  onApproveClicked() {
    const selectedInvoices = this.mySelection.map(id => this.gridData.find(x => x.Id === id));
    if (selectedInvoices.length > 0) {
      const ps: PromptSettings = {
        title: "Approve Invoices",
        content: "Are you sure you want to approve all line items for these selected invoices?",
        type: "Yes-No"
      }

      this.messageService.prompt(ps).then((result) => {
        if (result === promptAction.Yes)
          this.approveLines$.next(null);
      });
    }
  }

  downloadInvoice(invoiceNum: string, fileNameOriginal: string): void {
    const invoiceNumAndFileName: InvoiceNumAndFileName = { invoiceNum: invoiceNum, fileName: fileNameOriginal };
    this.downloadInvoice$.next(invoiceNumAndFileName);
  }

  export(): void {
    this.exportClicked$.next(null);
  }

  onSelectionsChanged(): void {
    //remove any selections that are not checkable. this is needed when "shift" is used to select multiple rows
    this.mySelection = this.mySelection.filter(id => this.isCheckable(id));

    if (this.mySelection.length > 0) {
      this.isAnyRowSelected.set(true);
    }
    else {
      this.isAnyRowSelected.set(false);
    }
  }

  selectAllToggled(e: MouseEvent) {
    const isCheckingAll = util.getCheckboxMetadata(e).isChecked;
    this.mySelection = util.selectAllGridItems(isCheckingAll, this.gridData, this.mySelection, this.isCheckable, item => item.Id);
    this.onSelectionsChanged();
  }


  isCheckable(invoiceId: number) {
    return invoiceId && invoiceId !== 0;
  }

  dataStateChange(state: State): void {
    this.gridScrollPosition.topPos = 0;
    this.gridScrollPosition.leftPos = 0;
    util.fixKendoQueryFilter(state.filter);
    this.state = state;
    this.refreshItems$.next(null);
  }

  convertMergables(): InvoiceSelection[] {
    return this.invoicesSelection = this.mySelection.filter(x => {
      const invoiceIdStr = x.toString();
      if (invoiceIdStr !== "null") {
        const invoiceItem = this.gridData.find(y => y.Id === parseInt(invoiceIdStr));
        const hasFile = invoiceItem && invoiceItem.FileNameOnDisk != null
        return hasFile;
      }
      return false;
    }).map(x => {
      const invoiceId = x;
      const selection: InvoiceSelection = { id: invoiceId };
      return selection;
    });
  }

  mergeFiles(): void {
    this.isMergingFiles$.next(true);
    this.mergeFiles$.next(null);
  }

  clearSelection(): void {
    this.mySelection = [];
    this.lastSelectedIndex = null;
    this.refreshItems$.next(null);
  }

  downloadInvoiceResult$ = this.downloadInvoice$.pipe(
    filter(invoiceNumAndFileName => {
      return invoiceNumAndFileName && invoiceNumAndFileName.invoiceNum !== null;
    }),
    switchMap(invoiceNumAndFileName => {
      return this.service.downloadInvoice(invoiceNumAndFileName.invoiceNum, invoiceNumAndFileName.fileName);
    }),
    tap(res => {
      util.openOrSaveFile(res.fileBlob, res.fileName);
    }),
    catchError(err => {
      return util.handleError(err, this.messageService);
    }), retry(10)
  )

  monthStartChanged$ = this.monthSelectionsForm.controls['monthStart'].valueChanges.pipe(
    tap(month => {
      this.monthSelectionsForm.patchValue({ monthEnd: month }, { emitEvent: false });
      this.clearSelection();
    })
  )

  monthEndChanged$ = this.monthSelectionsForm.controls['monthEnd'].valueChanges.pipe(
    tap(() => {
      this.clearSelection();
    })
  )

  requiredData$ = this.service.getRequiredData().pipe(
    tap((requiredData) => {
      this.localRequiredData = requiredData;
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(10)
  );

  linesResult$ = this.approveLines$.pipe(
    switchMap(() => {
      this.gridLoading.set(true);
      return this.service.approveInvoiceLines(this.mySelection);
    }),
    tap(() => {
      this.toast.success('approval successful');
      this.refreshItems$.next(null);
    }),
    shareReplay(1),
    catchError(err => {
      this.gridLoading.set(false);
      return util.handleError(err, this.messageService)
    }), retry(10)
  )

  items$ = this.refreshItems$.pipe(
    filter(() => {
      this.monthSelectionsForm.updateValueAndValidity({ emitEvent: false, onlySelf: true });
      const monthStart = this.monthSelectionsForm.value.monthStart;
      const monthEnd = this.monthSelectionsForm.value.monthEnd;
      return monthStart != null && monthEnd != null;
    }),
    tap(() => {
      this.gridLoading.set(true);
    }),
    util.fastDebounce(500),
    switchMap(() => {
      const monthStart = this.monthSelectionsForm.value.monthStart;
      const monthEnd = this.monthSelectionsForm.value.monthEnd;
      this.bcStatus$.next(null);
      return this.service.getInvoices(this.state, monthStart, monthEnd);
    }),
    map((results: GridDataResult) => {
      this.gridData = util.convertDatesToDateOnlyStrings(results.data, ['Month', 'DueDate']);
      return { data: this.gridData, total: results.total } as GridDataResult;
    }),
    tap(() => {
      const selectAllCheckbox: HTMLInputElement = this.fastGridEl.nativeElement.querySelector('.overviewSelectAllCheckbox');
      selectAllCheckbox.checked = false;
      this.lastSelectedIndex = null;
      this.gridLoading.set(false);
      util.goToSavedGridScrollPos(this.fastGridEl, this.gridScrollPosition);
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService);
    }), retry(10)
  )

  exportAction$ = this.exportClicked$.pipe(
    tap(() => {
      this.exporting$.next(true);
    }),
    switchMap(() => {
      const monthStart = this.monthSelectionsForm.value.monthStart;
      const monthEnd = this.monthSelectionsForm.value.monthEnd;
      if (monthStart !== null && monthStart !== undefined && monthEnd !== null && monthEnd !== undefined) {
        return this.service.exportInvoicesXls(monthStart, monthEnd, this.state, 'GasInvoice.xlsx');
      }
      else
        return of(null)
    }),
    tap(res => {
      if (res !== null) {
        util.openOrSaveFile(res.fileBlob, res.fileName);
      }
      this.exporting$.next(false);
    }),
    shareReplay(1),
    catchError(err => {
      this.exporting$.next(false);
      return util.handleError(err, this.messageService);
    }), retry(10)
  )

  mergeFilesResult$ = this.mergeFiles$.pipe(
    switchMap(() => {
      this.gridLoading.set(true);
      const invoicesToMerge = this.convertMergables();
      return this.service.mergeFiles(invoicesToMerge);
    }),
    tap(res => {
      this.isMergingFiles$.next(false);
      this.refreshItems$.next(null);

      if (res.fileName === 'Error.txt') {
        this.toast.error('An error occurred during file merge.  See "Error.txt".');
      }

      util.openOrSaveFile(res.fileBlob, res.fileName);
    }),
    catchError(err => {
      this.isMergingFiles$.next(false);
      this.gridLoading.set(false);
      return util.handleError(err, this.messageService);
    }), retry(10)
  )

  title$ = of('Gas Invoice').pipe(
    tap((title) => util.trySetTitle(this.titleService, title))
  )

  toggleSelection(e: MouseEvent, id: number, rowIndex: number) {
    e.stopPropagation();
    const result = util.toggleGridSelection({
      event: e,
      id: id,
      rowIndex: rowIndex,
      lastSelectedIndex: this.lastSelectedIndex,
      gridData: this.gridData,
      skip: this.state.skip ?? 0,
      selection: this.mySelection,
      isCheckable: this.isCheckable,
      getId: item => item.Id
    });
    this.mySelection = result.selection;
    this.lastSelectedIndex = result.lastSelectedIndex;
    this.onSelectionsChanged();
  }

  exportToBc() {
    const monthStr = dayjs(this.monthSelectionsForm.value.monthStart).format("MMMM YYYY");

    const ps: PromptSettings = {
      title: "Export to Business Central",
      content: "You are about to export all finalized invoices for " + monthStr + " with Business Central.\nAre you sure want to continue?",
      type: "Yes-No"
    }

    this.messageService.prompt(ps).then((result) => {
      if (result === promptAction.Yes) {
        this.exportToBusinessCentral$.next(null);
      }
    });
  }

  bcExportAction$ = this.exportToBusinessCentral$.pipe(
    tap(() => {
      this.bcExporting$.next(true);
    }),
    switchMap(() => {
      const monthStart = this.monthSelectionsForm.value.monthStart;
      if (monthStart !== null && monthStart !== undefined) {
        return this.service.exportToBc(monthStart);
      }
      else
        this.toast.success('Please select a month to export.');
    }),
    tap((resultStr) => {
      this.bcExporting$.next(false);
      this.messageService.info(resultStr);
      this.bcStatus$.next(null);
    }),
    shareReplay(1),
    catchError(err => {
      this.bcExporting$.next(false);
      this.bcStatus$.next(null);
      return util.handleError(err, this.messageService);
    }), retry(10)
  )

  bcStatusAction$ = this.bcStatus$.pipe(
    switchMap(() => {
      const monthStart = this.monthSelectionsForm.value.monthStart;
      if (monthStart !== null && monthStart !== undefined) {
        return this.service.getLatestStatus(monthStart);
      }
      else
        return of(null);
    }),
    tap((statusResponse) => {
      const status = statusResponse?.status ?? null;
      this.currentStatus.set(statusResponse ?? null);

      if (status === null || status === undefined) {
        this.bcExportStatus.set('None');
      }
      else if (status === 'Error') {
        this.bcExportStatus.set('Error');
      }
      else if (status === 'Success') {
        this.bcExportStatus.set('Success');
      }
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService);
    }), retry(10
    ))

  onSummaryMouseover(e: MouseEvent, tooltip: FastTipDirective) {
    const mousedElement = e.target as HTMLElement;
    tooltip.show(mousedElement);
  }
}
