import { Component, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild, ElementRef, ViewEncapsulation, HostListener, inject } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { tap, map, catchError, switchMap, filter, shareReplay, retry } from 'rxjs/operators';
import { of, BehaviorSubject, Subject, combineLatest, Observable } from 'rxjs';
import { State } from '@progress/kendo-data-query';
import { MessageService, promptAction } from '../_shared/services/message.service';
import { FormsModule, UntypedFormArray, Validators } from '@angular/forms';
import { CustomFormBuilder } from '../_shared/services/custom-form-builder.service';
import { NotifyService } from '../_shared/services/notify.service';
import { TooltipDirective } from '@progress/kendo-angular-tooltip';
import * as util from '../_shared/utils/util';
import { DetailMeter, Item, QualityBankService, RequiredData, ContractItem, Detail, CounterpartyItem, MeterItem } from './quality-bank.service';
import { SaveType } from '../_shared/utils/util';
import dayjs from 'dayjs';
import { FAST_KENDO_COMMON, FAST_PAGE_COMMON } from '../app.config';
import { ContextMenuSelectEvent, KENDO_CONTEXTMENU } from '@progress/kendo-angular-menu';
import { GridDataResult } from '@progress/kendo-angular-grid';
import { FastComboboxComponent } from '../_shared/elements/fast-combobox.component';

enum contextMenuEnum {
  CopyDown,
  CopyUp,
}

interface ContextMenuItem {
  enum: contextMenuEnum;
  text: string;
}

@Component({
  selector: 'app-qualitybank',
  standalone: true,
  imports: [FAST_KENDO_COMMON, FAST_PAGE_COMMON, FormsModule, KENDO_CONTEXTMENU],
  templateUrl: './quality-bank.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class QualityBankComponent {
  @ViewChild("grid", { read: ElementRef }) kendoGridEl: ElementRef;
  @HostListener('window:resize') onResize() {
    //this function is empty but for some reason it helps the window to resize faster
  };

  util = util;
  window = window;
  detailForm: util.FormModel<Detail> = null;
  detailMeterFormArray: UntypedFormArray
  showHistorical: boolean = false;
  gridScrollPosition: util.GridScrollPosition = { topPos: 0, leftPos: 0 };
  localRequiredData: RequiredData;
  hasModifyPermission = false;
  detailInitialValues: Detail;
  mySelection: number[] = [];
  selectedContract: DetailMeter;
  currentDetailId: number;

  refreshContractsforCounterparty$ = new Subject<string>()
  requiredData$: Observable<RequiredData>;
  filterCounterparty$: BehaviorSubject<string>
  filterContracts$: BehaviorSubject<string>
  filterMeter$: BehaviorSubject<string>
  meter$: Observable<MeterItem[]>;
  counterparty$: Observable<CounterpartyItem[]>;
  contracts$: Observable<ContractItem[]>;
  exportAction$: Observable<{ fileBlob: Blob; fileName: string; }>;
  gridLoading$ = new BehaviorSubject<boolean>(true)
  refreshItems$ = new BehaviorSubject<string>(null)
  exporting$ = new BehaviorSubject<boolean>(false)
  detailOpened$ = new BehaviorSubject<boolean>(false)
  refreshRequiredData$ = new BehaviorSubject(null)
  detailLoading$ = new BehaviorSubject<boolean>(true)
  detail$: Observable<Detail>;
  counterpartyChanged$: Observable<number>;
  refreshDetail$ = new BehaviorSubject<number>(null)
  saveDialogOpened$ = new BehaviorSubject<boolean>(false)
  save$ = new Subject<SaveType>()
  saveResult$: Observable<number>;
  deleteResult$: Observable<object>;
  delete$ = new Subject()
  exportClicked$ = new Subject()
  items$: Observable<GridDataResult>;

  state: State = {
    filter: null,
    group: null,
    skip: 0,
    sort: [{ field: 'CounterPartyName', dir: 'asc' }],
    take: 50
  };

  contextMenuCopyItems: ContextMenuItem[] = [
    { enum: contextMenuEnum.CopyUp, text: 'Copy Up' },
    { enum: contextMenuEnum.CopyDown, text: 'Copy Down' }
  ];

  service = inject(QualityBankService);
  messageService = inject(MessageService);
  titleService = inject(Title);
  fb = inject(CustomFormBuilder);
  notify = inject(NotifyService);
  ref = inject(ChangeDetectorRef);

  constructor() {
    this.detailForm = this.getDetailForm();
    this.detailInitialValues = this.detailForm.getRawValue() as Detail;

    this.counterpartyChanged$ = this.detailForm.get('counterpartyId').valueChanges.pipe(
      filter(() => {
        return !this.detailLoading$.value;
      }),
      tap(() => {
        this.refreshContractsforCounterparty$.next(null);
      }),
      shareReplay(1)
    )

    this.requiredData$ = this.refreshRequiredData$.pipe(
      tap(() => this.detailLoading$.next(true)),
      switchMap(refreshType => {
        return combineLatest([this.service.requiredData$, of(refreshType)]);
      }),
      map(([requiredData, refreshType]) => {
        this.localRequiredData = requiredData;
        if (refreshType === util.RefreshType.SelfOnly)
          this.detailLoading$.next(false);
        return requiredData;
      }),
      tap((requiredData) => {
        this.hasModifyPermission = requiredData.hasModifyPermission;
      }),
      shareReplay(1),
      catchError(err => {
        return util.handleError(err, this.messageService)
      }), retry(10)
    )

    this.items$ = this.refreshItems$.pipe(
      tap(() => {
        this.gridLoading$.next(true);
      }),
      switchMap(() => {
        return this.service.getItems(this.state, this.showHistorical);
      }),
      tap(() => {
        this.gridLoading$.next(false);
        util.goToSavedGridScrollPos(this.kendoGridEl, this.gridScrollPosition);
      }),
      shareReplay(1),
      catchError(err => {
        return util.handleError(err, this.messageService);
      }), retry(10)
    )

    this.exportAction$ = this.exportClicked$.pipe(
      tap(() => {
        this.exporting$.next(true);
      }),
      switchMap(() => {
        return this.service.exportItems(this.state, 'QualityBankAdjustments.xlsx');
      }),
      tap(res => {
        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)
    )

    this.detail$ = this.refreshDetail$.pipe(
      filter(id => id !== null),
      switchMap(id => {
        this.detailLoading$.next(true);
        this.detailForm.disable();
        this.detailForm.reset();
        if (id === 0) {
          return of(this.detailInitialValues)
        }
        else
          return this.service.getDetail(id);
      }),
      map(detail => {
        util.convertToDates(detail, []);
        this.createDetailMeters(detail);
        this.detailForm.setValue(detail);


        return detail;
      }),
      tap(() => {
        this.detailFinishedLoading();
        this.refreshContractsforCounterparty$.next(null);
      }),
      shareReplay(1),
      catchError(err => {
        this.closeDetail(false);
        return util.handleError(err, this.messageService)
      }), retry(10)
    )

    this.saveResult$ = this.save$.pipe(
      switchMap(saveType => {
        this.detailLoading$.next(true);
        const itemToSave: Detail = this.detailForm.getRawValue();
        this.detailForm.disable();
        this.detailMeterFormArray.disable();
        return this.service.saveDetail(itemToSave, saveType);
      }),
      tap(saveResult => {
        this.notify.success('save successful');
        this.closeDetail(false);
        this.detailLoading$.next(false);
        this.refreshItems$.next(null);
        this.mySelection = [saveResult];
      }),
      shareReplay(1),
      catchError(err => {
        this.detailFinishedLoading();
        return util.handleError(err, this.messageService)
      }), retry(10)
    )

    this.deleteResult$ = this.delete$.pipe(
      switchMap(() => {
        this.detailLoading$.next(true);
        this.detailForm.disable();
        const idToDelete = this.currentDetailId;
        return this.service.deleteDetail(idToDelete);
      }),
      tap(() => {
        this.notify.success('delete successful');
        this.detailFinishedLoading();
        this.closeDetail(false);
        this.refreshItems$.next(null);
      }),
      shareReplay(1),
      catchError(err => {
        return util.handleError(err, this.messageService)
      }), retry(10)
    )

    this.filterMeter$ = new BehaviorSubject<string>(null)
    this.meter$ = this.filterMeter$.pipe(util.filterSpecials(this.detailLoading$, this.requiredData$, 'meters', 'meterName'));

    this.filterCounterparty$ = new BehaviorSubject<string>(null)
    this.counterparty$ = this.filterCounterparty$.pipe(util.filterSpecials(this.detailLoading$, this.requiredData$, 'counterparties', 'counterpartyName'));

    this.filterContracts$ = new BehaviorSubject<string>(null)
    this.contracts$ = this.filterContracts$.pipe(util.filterSpecials(this.detailLoading$, this.subContractsForCounterparty$, null, 'contractNum'));

  }

  getDetailForm() {
    const currentAsOfDate = dayjs().format('YYYY-MM-DD');
    const currentMonth = dayjs().format('MMMM YYYY');
    const fb = this.fb;
    const fg: util.FormModel<Detail> = fb.group({
      id: fb.ctr(null),
      month: fb.ctr(currentMonth, Validators.required),
      counterpartyId: fb.ctr(null, Validators.required),
      contractId: fb.ctr(null, Validators.required),
      asOfDate: fb.ctr(currentAsOfDate, Validators.required),
      detailMeters: fb.arr([])
    });
    return fg;
  }

  getDetailMeterForm(item: DetailMeter) {
    const fb = this.fb;
    const fg: util.FormModel<DetailMeter> = fb.group({
      meterId: fb.ctr(item.meterId, Validators.required),
      titleMeterId: fb.ctr(null, Validators.required),
      qb1: fb.ctr(item.qb1),
      qb2: fb.ctr(item.qb2),
      qb3: fb.ctr(item.qb3),
      qb4: fb.ctr(item.qb4),
    });

    return fg;
  }

  openDetail(id: number): void {
    util.saveGridScrollPos(this.kendoGridEl, this.gridScrollPosition);
    this.currentDetailId = id;
    this.refreshDetail$.next(id);
    if (id === 0) {
      this.addMeterItem();
    }
    this.detailOpened$.next(true);
  }

  createDetailMeters(detail: Detail) {
    this.detailMeterFormArray = this.detailForm.get('detailMeters') as UntypedFormArray;
    this.detailMeterFormArray.clear();
    for (let i = 0; i < detail.detailMeters.length; i++) {
      this.detailMeterFormArray.push(this.getDetailMeterForm(detail.detailMeters[i]));
    }
  }

  onDetailClosing() {
    const isTopInfoDirty = this.detailForm.dirty;
    const isMetersArrayDirty = this.detailMeterFormArray.dirty;
    if (isTopInfoDirty || isMetersArrayDirty) {
      const message = "You have unsaved changes. What would you like to do?";
      this.messageService.prompt(message, "Please confirm", 'Save-DontSave-Cancel').then(result => {
        if (result === promptAction.Save)
          this.save(util.SaveType.Normal);
        else if (result === promptAction.DontSave)
          this.closeDetail(false);
        else if (result === promptAction.Cancel)
          return;
      });
    }
    else {
      this.closeDetail(false);
    }
  }

  closeDetail = (isFromInterface: boolean) => {
    this.detailOpened$.next(false);
    this.saveDialogOpened$.next(false);

    if (isFromInterface)
      util.goToSavedGridScrollPos(this.kendoGridEl, this.gridScrollPosition);
  }

  detailFinishedLoading(): void {
    this.detailForm.enable();
    this.detailLoading$.next(false);
    // this.checkTooltipVisibility(this.meterCombobox, this.meterTooltip);
  }

  add(): void {
    this.openDetail(0);
  }

  edit(dataItem: Item): void {
    this.openDetail(dataItem.Id);
  }

  save = (saveType: util.SaveType) => {
    let detailValid: boolean = false;
    detailValid = this.validateDetail();
    if (detailValid) {
      this.save$.next(saveType);
    }
    return saveType
  }

  validateDetail(): boolean {
    this.detailForm.markAllAsTouched();
    this.detailMeterFormArray.markAllAsTouched();
    const meters = this.detailForm.get('detailMeters').value;
    const isTopInfoValid = this.detailForm.valid;
    const isMeterFormValid = this.detailMeterFormArray.valid && meters.length > 0;
    if (!isTopInfoValid || !isMeterFormValid) {
      this.notify.error('validation failed');
      return false;
    }
    if (isTopInfoValid && isMeterFormValid) {
      return true;
    }
  }

  delete(): void {
    const message = "Are you sure you want to delete this item?";
    this.messageService.prompt(message, "Please confirm", 'Yes-No').then(result => {
      if (result === promptAction.Yes)
        this.delete$.next(null);
    });
  }

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

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

  showHistoricalChanged(showHistorical: boolean) {
    this.showHistorical = showHistorical;
    this.refreshItems$.next(null);
  }

  addMeterItem() {
    this.detailMeterFormArray.insert(0, this.getDetailMeterForm({
      meterId: null,
      titleMeterId: null,
      qb1: null,
      qb2: null,
      qb3: null,
      qb4: null,
    }));
  }

  removeDetailMeter(idx: number) {
    this.detailMeterFormArray.removeAt(idx);
    this.detailMeterFormArray.markAsDirty();
  }

  onContextMenuSelect(e: ContextMenuSelectEvent, controlName: string, index: number) {
    const contextMenuItem: ContextMenuItem = e.item;
    if (contextMenuItem.enum === contextMenuEnum.CopyUp)
      this.CopyUp(controlName, index, true);
    else if (contextMenuItem.enum === contextMenuEnum.CopyDown)
      this.CopyDown(controlName, index, true);
  }

  private CopyUp(controlName: string, index: number, markDirty: boolean) {
    const controls = this.detailMeterFormArray.controls;
    const valueToCopy = this.detailMeterFormArray.controls[index].value[controlName];
    for (let i = index - 1; i >= 0; i--) {
      controls[i].patchValue({ [controlName]: valueToCopy });
      if (markDirty)
        controls[i].markAsDirty();
    }
  }

  private CopyDown(controlName: string, index: number, markDirty: boolean) {
    const controls = this.detailMeterFormArray.controls;
    const valueToCopy = this.detailMeterFormArray.controls[index].value[controlName];
    for (let i = index + 1; i < controls.length; i++) {
      controls[i].patchValue({ [controlName]: valueToCopy });
      if (markDirty)
        controls[i].markAsDirty();
    }
  }

  checkTooltipVisibility(mouseEvent: MouseEvent, tooltip: TooltipDirective): void {
    tooltip.hide();
    tooltip.showOn = 'none';
    const inputMoused = mouseEvent && mouseEvent !== undefined;
    if (!inputMoused) {
      return;
    }
    const mousedElement = mouseEvent.target as HTMLInputElement;
    if (!mousedElement) {
      return;
    }
    const text = mousedElement.value;
    const isLargeText = text && text !== undefined && text.length >= 20;
    if (isLargeText) {
      tooltip.toggle(mousedElement, true);
    }
  }

  closeTooltip(tooltip: TooltipDirective): void {
    tooltip.hide();
  }

  getComboboxText(combobox: FastComboboxComponent<string>,): string {
    const textWidth = combobox.value.length;
    if (textWidth > 20) {
      return combobox.value();
    }
    else {
      return '';
    }
  }


  title$ = of('Quality Bank Adjustments').pipe(
    tap((title) => util.trySetTitle(this.titleService, title))
  )

  subContractsForCounterparty$ = this.refreshContractsforCounterparty$.pipe(
    map(() => {
      const counterpartyId = this.detailForm.get('counterpartyId').value ?? null;
      let subContracts: ContractItem[] = [];

      if (counterpartyId)
        subContracts = this.localRequiredData.contracts.filter(x => x.counterpartyId === counterpartyId);

      const contractId = this.detailForm.get('contractId').getRawValue();
      if (subContracts && subContracts.findIndex(x => x.contractId === contractId) === -1)
        this.detailForm.patchValue({ contractId: null }, { emitEvent: false });

      if (subContracts.length === 1)
        this.detailForm.patchValue({ contractId: subContracts[0].contractId }, { emitEvent: false });

      return subContracts;
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(10)
  )

}
