import { Component, ChangeDetectionStrategy, AfterViewInit, ViewChild, ElementRef, ChangeDetectorRef, ViewEncapsulation, TemplateRef, AfterViewChecked, inject, signal } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { tap, map, catchError, switchMap, shareReplay, filter, mergeMap, retry } from 'rxjs/operators';
import { of, BehaviorSubject, Subject, combineLatest } from 'rxjs';
import { ReportService, Report, ReportDetail, ReportFilter } from './report.service';
import { ReportFilterService } from '../report-filter/report-filter.service';
import { MessageService, promptAction, PromptSettings } from '../_shared/services/message.service';
import { FormBuilder, FormGroup, FormsModule, Validators } from '@angular/forms';
import { ToastService } from '../_shared/services/fast-toast.service';
import { KENDO_AUTOCOMPLETE, PreventableEvent } from '@progress/kendo-angular-dropdowns';
import { FileRestrictions, SelectEvent, SuccessEvent } from '@progress/kendo-angular-upload';
import { saveAs } from '@progress/kendo-file-saver';
import { animate, transition, trigger, style } from '@angular/animations';
import * as util from '../_shared/utils/util';
import { FAST_KENDO_COMMON, FAST_PAGE_COMMON } from '../app.config';
import { NgStyle, NgClass } from '@angular/common';
import { ReportFilterComponent } from '../report-filter/report-filter.component';
import { FastAutocompleteComponent } from '../_shared/elements/fast-autocomplete.component';
import { FastUploadComponent } from '../_shared/elements/fast-upload.component';

export interface FileNamePair {
  fileNameOriginal: string;
  fileNameOnDisk: string;
}

export interface RunningItem {
  runningId: number;
  name: string;
}

@Component({
  selector: 'report',
  templateUrl: './report.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  imports: [FAST_PAGE_COMMON, FAST_KENDO_COMMON, KENDO_AUTOCOMPLETE, FormsModule, NgStyle, NgClass, ReportFilterComponent],
  animations: [
    trigger('animateRunningItem', [
      transition(':enter', [
        style({ 'transform': 'scale(1.5)' }),
        animate("1s ease", style({ 'transform': 'scale(1)' }))
      ]),
      transition(':leave', [
        animate("0.5s ease", style({ 'opacity': '0' }))
      ])
    ])
  ]
})
export class ReportComponent implements AfterViewInit, AfterViewChecked {
  @ViewChild('reportSearchTarget') reportSearchElem: FastAutocompleteComponent<never>;
  @ViewChild('firstEditorInputTarget') firstEditorInputElem: ElementRef;
  @ViewChild('templateFileUploadTarget') templateFileUploadElem: FastUploadComponent;
  @ViewChild("reportList") reportListEl: ElementRef;

  private messageService = inject(MessageService);
  private titleService = inject(Title);
  private reportService = inject(ReportService);
  private reportFilterService = inject(ReportFilterService);
  private fb = inject(FormBuilder);
  private ref = inject(ChangeDetectorRef);
  private toast = inject(ToastService);

  animationInitialized = false;
  loading = true;
  scrollPending = false;

  formEditor: FormGroup;
  editorOpened = signal(false);
  filterEditorOpened = signal(false);

  hasReportModifyPermission = false;
  runningContainerVisible = false;
  hasTemplateFile = false;
  runningId = 0;
  runningReports: RunningItem[] = [];
  templateSaveUrl = `${window.location.origin}/api/Report/SaveReportTemplate`;
  showAllReports = false;
  localReport: Report;

  scrollPos: number;
  selectedReportName: string;

  templateRestrictions: FileRestrictions = {
    allowedExtensions: ['xlsx', 'xlsm', 'xlsb', 'xls']
  };

  constructor() {
    this.formEditor = this.fb.group({
      id: [null, Validators.required],
      name: [null, Validators.required],
      dataSourceIds: [null, Validators.required],
      templateFileNameOriginal: [null],
      templateFileNameOnDisk: [null],
      securityGroupIds: [null],
      securityUserIds: [null],
      notes: [null]
    });

    this.formEditor.disable();

    this.formEditor.controls['templateFileNameOriginal'].valueChanges.subscribe((value: string) => {
      this.hasTemplateFile = value && value.length > 0;
    });
  }

  getScrollContentElement(): Element {
    const el = this.reportListEl.nativeElement as HTMLElement;
    return el;
  }

  saveScrollPos() {
    this.scrollPos = this.getScrollContentElement()?.scrollTop ?? 0;
  }

  restoreScrollPos() {
    const content = this.getScrollContentElement();
    if (content)
      content.scrollTo({ top: this.scrollPos });
    this.scrollPending = false;
  }

  goToSavedScrollPos() {
    // will be handled in ngAfterViewChecked
    this.scrollPending = true;
  }

  ngAfterViewChecked() {
    //runs after every change detection cycle
    if (this.scrollPending)
      // a promise waits one more tick for the DOM to be stable
      Promise.resolve().then(() => this.restoreScrollPos());
  }

  editorLoading$ = new BehaviorSubject<boolean>(true); //this needs to be observable for filterIdNames

  refreshReports$ = new BehaviorSubject<string>(null)
  refreshReportDetail$ = new BehaviorSubject<number>(null)
  refreshRequiredData$ = new BehaviorSubject(null)
  refreshItemFilters$ = new BehaviorSubject<number>(null)

  filterReports$ = new BehaviorSubject<string>(null)
  filterDataSources$ = new BehaviorSubject<string>(null)
  filterSecurityGroups$ = new BehaviorSubject<string>(null)
  filterUserInfos$ = new BehaviorSubject<string>(null)
  filterFilters$ = new BehaviorSubject<string>(null)

  downloadTemplateFile$ = new Subject<FileNamePair>()
  runReport$ = new Subject<Report>()
  setFilterSelected$ = new Subject<{ reportId: number; filterId: number }>()
  setSaveAsPdf$ = new Subject<{ reportId: number; saveAsPdf: boolean }>()

  saveReport$ = new Subject()
  deleteReport$ = new Subject()
  copyFilter$ = new Subject<number>()

  ngAfterViewInit(): void {
    this.reportSearchElem.focus();
  }

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

  getMaxWindowHeight() {
    return window.innerHeight;
  }

  getMaxWindowWidth() {
    return window.innerWidth;
  }

  requiredData$ = this.refreshRequiredData$.pipe(
    switchMap(() => {
      return this.reportService.requiredData$
    }),
    tap((res) => {
      this.hasReportModifyPermission = res.hasReportModifyPermission;

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

  dataSources$ = this.filterDataSources$.pipe(util.filterIdNames(this.editorLoading$, this.requiredData$, 'dataSources'));
  securityGroups$ = this.filterSecurityGroups$.pipe(util.filterIdNames(this.editorLoading$, this.requiredData$, 'securityGroups'));
  userInfos$ = this.filterUserInfos$.pipe(util.filterIdNames(this.editorLoading$, this.requiredData$, 'userInfos'));

  reports$ = this.refreshReports$.pipe(
    tap(() => {
      this.loading = true
    }),
    switchMap(() => {
      return this.reportService.getReports(this.showAllReports);
    }),
    map(reports => {
      this.loading = false;
      return reports;
    }),
    shareReplay(1),
    catchError(err => {
      this.loading = false;
      return util.handleError(err, this.messageService)
    }), retry(3)
  );

  reportDetail$ = this.refreshReportDetail$.pipe(
    filter(id => id !== null),
    switchMap(id => {
      return this.reportService.getReportDetail(id);
    }),
    tap((reportDetail) => {
      this.setEditorItem(reportDetail);
      this.editorFinishedLoading();
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(3)
  )

  reportsFiltered$ = combineLatest([this.reports$, this.filterReports$, this.filterFilters$]).pipe(
    switchMap(([reports, filterText]) => {
      if (filterText && filterText.length > 0) {
        filterText = filterText.toLowerCase();
        reports = reports.filter((report: Report) => {
          const isMatch: boolean = report.name.toLowerCase().includes(filterText);
          return isMatch;
        })
      }
      return of(reports);
    }),
    tap(() => {
      this.goToSavedScrollPos();
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(3)
  )

  saveReportResult$ = this.saveReport$.pipe(
    switchMap(() => {
      const itemToSave: ReportDetail = this.formEditor.value;
      this.formEditor.disable();
      return this.reportService.saveReportDetail(itemToSave);
    }),
    tap(() => {
      this.toast.success("save successful");
      this.editorFinishedLoading();
      this.editorOpened.set(false);
      this.refreshReports$.next(null);
    }),
    catchError(err => {
      this.editorFinishedLoading();
      return util.handleError(err, this.messageService)
    }), retry(3)
  )

  deleteReportResult$ = this.deleteReport$.pipe(
    switchMap(() => {
      const itemToDelete: ReportDetail = this.formEditor.value;
      return this.reportService.deleteReport(itemToDelete.id);
    }),
    tap(() => {
      this.toast.success('delete successful');
      this.editorFinishedLoading();
      this.editorOpened.set(false);
      this.refreshReports$.next(null);
    }),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(3)
  )

  copyFilterResult$ = this.copyFilter$.pipe(
    switchMap((filterId) => {
      return this.reportFilterService.copyFilterToAllUsers(filterId);
    }),
    tap(() => {
      this.toast.success('filter copied');
      this.loading = false;
    }),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(3)
  )

  downloadTemplateFileResult$ = this.downloadTemplateFile$.pipe(
    filter(fileNamePair => {
      return fileNamePair && fileNamePair.fileNameOnDisk !== null;
    }),
    switchMap(fileNamePair => {
      const reportId: number = this.formEditor.get('id').value;
      return this.reportService.downloadReportTemplate(reportId, fileNamePair.fileNameOriginal, fileNamePair.fileNameOnDisk);
    }),
    tap(res => {
      saveAs(res.fileBlob, res.fileName);
    }),
    catchError(err => {
      return util.handleError(err, this.messageService);
    }), retry(3)
  )

  runReportResult$ = this.runReport$.pipe(
    filter(report => {
      let hasRequiredFilterParams: boolean;
      if (!report.selectedFilterId)
        hasRequiredFilterParams = false;
      else
        hasRequiredFilterParams = report.filters.findIndex(x => x.id === report.selectedFilterId && x.hasRequiredFilterParams) > -1;

      const missingRequiredFilter = report.hasRequiredFilter && !hasRequiredFilterParams;
      if (missingRequiredFilter)
        this.messageService.throw("This report has a data source which requires a filter.");

      return report !== null && !(missingRequiredFilter);
    }),
    mergeMap(report => {
      //briefly disable the run button so that users don't accidentally run a report more than once
      report.runDisabled = true;
      setTimeout(() => {
        report.runDisabled = false;
        this.ref.detectChanges();
      }, 500);

      this.runningContainerVisible = true;
      this.runningId++;
      const runningItem: RunningItem = { runningId: this.runningId, name: report.name }
      this.runningReports.push(runningItem);
      return combineLatest([of(this.runningId), this.reportService.runReport(report.id, report.selectedFilterId, report.saveAsPdf)]);
    }),
    tap(([runningId, result]) => {
      this.runningReports = this.runningReports.filter(x => x.runningId !== runningId);
      saveAs(result.fileBlob, result.fileName);

      //this gives time for the :leave animation to finish before setting visibility
      setTimeout(() => {
        this.runningContainerVisible = this.runningReports?.length > 0;
        this.ref.detectChanges();
      }, 500);
    }),
    catchError(err => {
      return util.handleError(err, this.messageService)
    })
  )

  setFilterSelectedResult$ = this.setFilterSelected$.pipe(
    mergeMap((item) => {
      return this.reportFilterService.setFilterSelected(item.reportId, item.filterId)
    })
  )

  setSaveAsPdfResult$ = this.setSaveAsPdf$.pipe(
    mergeMap((item) => {
      return this.reportService.setSaveAsPdf(item.reportId, item.saveAsPdf)
    })
  )

  setEditorItem(editorItem: ReportDetail): void {
    this.formEditor.setValue(editorItem);
  }

  openEditor(reportId: number): void {
    this.saveScrollPos();
    this.formEditor.disable();
    this.editorLoading$.next(true);

    this.formEditor.reset();
    //set new values for an 'add' action
    this.formEditor.patchValue({ id: reportId });

    this.editorOpened.set(true);
  }

  editorFinishedLoading(): void {
    this.editorLoading$.next(false);
    this.formEditor.enable();
  }

  addReport(): void {
    this.openEditor(0);
    this.editorFinishedLoading();
  }

  editReport(id: number): void {
    if (this.hasReportModifyPermission) {
      this.openEditor(id);
      this.refreshReportDetail$.next(id);
    }
  }

  openFilterEditor(report: Report): void {
    this.saveScrollPos();
    this.localReport = report;
    this.filterEditorOpened.set(true);
  }

  saveReport = () => {
    this.formEditor.markAllAsTouched();
    if (this.formEditor.valid) {
      this.editorLoading$.next(true);
      this.saveReport$.next(null);
    } else {
      this.toast.error("Validation failed");
    }
  }

  deleteReport(): void {
    const ps: PromptSettings = {
      title: "Please Confirm",
      content: "Are you sure you want to delete this report?",
      type: 'Yes-No'
    }

    this.messageService.prompt(ps).then((result) => {
      if (result === promptAction.Yes) {
        this.formEditor.disable();
        this.editorLoading$.next(true);
        this.deleteReport$.next(null);
      }
    });
  }

  copyFilter(template: TemplateRef<unknown>, selectedFilterId: number): void {
    const ps: PromptSettings = {
      title: "Please Confirm",
      content: template,
      type: 'Yes-No'
    }

    this.messageService.prompt(ps).then((result) => {
      if (result === promptAction.Yes) {
        this.loading = true;
        this.copyFilter$.next(selectedFilterId);
      }
    });
  }

  filterReports($event: string): void {
    this.filterReports$.next($event);
  }

  showAllReportsToggle($event: boolean): void {
    this.showAllReports = $event;
    this.refreshReports$.next(null);
  }

  runReport(report: Report): void {
    this.runReport$.next(report);
  }

  filterDataSources(value: string) {
    this.filterDataSources$.next(value);
  }

  filterSecurityGroups(value: string) {
    this.filterSecurityGroups$.next(value);
  }

  filterUserInfos(value: string) {
    this.filterUserInfos$.next(value);
  }

  templateUploadSelect(value: SelectEvent) {
    const extension: string = value.files[0].extension.replace('.', '');
    if (extension && extension.length > 0 && this.templateRestrictions.allowedExtensions.includes(extension)) {
      this.formEditor.disable();
      this.editorLoading$.next(true);
    }
    else {
      this.toast.error("Invalid file type");
      this.clearTemplateFile();
    }
  }

  templateUploadSuccess(value: SuccessEvent) {
    this.formEditor.markAsDirty();
    const fileNameOriginal: string = value.response.body.fileNameOriginal;
    const fileNameOnDisk: string = value.response.body.fileNameOnDisk;
    this.formEditor.patchValue({ templateFileNameOriginal: fileNameOriginal });
    this.formEditor.patchValue({ templateFileNameOnDisk: fileNameOnDisk });
    this.editorFinishedLoading();
  }

  templateUploadError() {
    this.editorFinishedLoading();
  }

  downloadTemplateFile() {
    const templateFileNameOriginal: string = this.formEditor.controls['templateFileNameOriginal'].value;
    const templateFileNameOnDisk: string = this.formEditor.controls['templateFileNameOnDisk'].value;
    const fileNamePair: FileNamePair = { fileNameOriginal: templateFileNameOriginal, fileNameOnDisk: templateFileNameOnDisk };
    this.downloadTemplateFile$.next(fileNamePair)
  }

  clearTemplateFile() {
    this.formEditor.markAsDirty();
    this.formEditor.patchValue({ templateFileNameOriginal: null });
    this.formEditor.patchValue({ templateFileNameOnDisk: null });
    this.templateFileUploadElem.clearFiles();
  }

  onFilterEditorClosed() {
    this.refreshReports$.next(null);
    this.filterEditorOpened.set(false);
  }

  onFilterSelected(reportfilter: ReportFilter, reportId: number) {
    this.setFilterSelected$.next({ reportId: reportId, filterId: reportfilter?.id });
  }

  onSaveAsPdfChanged(saveAsPdf: boolean, reportId: number) {
    this.setSaveAsPdf$.next({ reportId: reportId, saveAsPdf: saveAsPdf });
  }

  filterFilters(value: string) {
    this.filterFilters$.next(value);
  }

  reportSearchOpening(event: PreventableEvent) {
    event.preventDefault();
  }

  onDetailClosing() {
    util.onDetailChanging(this.formEditor, this.messageService, this.closeEditor, this.saveReport);
  }

  closeEditor = () => {
    this.editorOpened.set(false);
  }
}
