import { ChangeDetectionStrategy, Component, HostListener, inject, input, output, ViewEncapsulation } from '@angular/core';
import * as util from '../_shared/utils/util';
import { Observable, map, catchError, tap, combineLatest, of, retry, shareReplay, switchMap, BehaviorSubject, Subject, take } from 'rxjs';
import { FAST_KENDO_COMMON, FAST_PAGE_COMMON } from '../app.config';
import { Title } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
import { DialogService, DialogSettings } from '@progress/kendo-angular-dialog';
import { MessageService } from '../_shared/services/message.service';
import { CustomFormBuilder } from '../_shared/services/custom-form-builder.service';
import { NotifyService } from '../_shared/services/notify.service';
import { MeterProductService, ProductDetail, RequiredData } from './meter-product.service';
import { Validators } from '@angular/forms';


@Component({
  selector: 'app-meter-product',
  templateUrl: './meter-product.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [FAST_PAGE_COMMON, FAST_KENDO_COMMON]
})
export class MeterProductComponent {
  @HostListener('window:resize') onResize() {
    //this function is empty but for some reason it helps the window to resize faster
  };

  private service = inject(MeterProductService);
  private messageService = inject(MessageService);
  private titleService = inject(Title);
  private fb = inject(CustomFormBuilder);
  private activatedRoute = inject(ActivatedRoute);
  private dialogService = inject(DialogService);
  private notify = inject(NotifyService);

  // inputs, outputs, required variables for the program
  meterId = input.required<number>();
  productId = input.required<number>();
  effectiveDate = input<Date>();
  closed = output();
  util = util;

  productName = '';
  productDetailForm = this.getProductDetailForm();
  localRequiredData: RequiredData;
  hasModifyPermission = false;
  detailInitialValues: ProductDetail;

  // detail related observables and behaviorsubjects
  requiredData$: Observable<RequiredData>;
  detail$: Observable<ProductDetail>;
  refreshRequiredData$ = new BehaviorSubject<util.RefreshType>(null);
  refreshDetail$ = new Subject<void>();
  productDetailLoading$ = new BehaviorSubject<boolean>(true);

  // required data observables and behavior subjects
  filterMeterTypes$ = new BehaviorSubject<string>(null);
  meterTypes$: Observable<util.IdName[]>;
  filterSourcePipes$ = new BehaviorSubject<string>(null);
  sourcePipes$: Observable<util.Pipe[]>;
  filterDeliveryPipes$ = new BehaviorSubject<string>(null);
  filterSourceZones$ = new BehaviorSubject<string>(null);
  sourceZones$: Observable<util.Zone[]>;
  filterPlants$ = new BehaviorSubject<string>(null);
  plantNames$: Observable<util.IdName[]>;
  filterSourcePoints$ = new BehaviorSubject<string>(null)
  filterDeliveryPoints$ = new BehaviorSubject<string>(null)
  sourcePointNames$: Observable<util.Point[]>;
  deliveryPointNames$: Observable<util.Point[]>;

  refreshSourceZonesForPipe$ = new Subject<string>();
  refreshSourcePointsForPipe$ = new Subject<string>();
  refreshDeliveryPointsForPipe$ = new Subject<string>();
  subSourcePointsForSourcePipeId$: Observable<util.Point[]>;
  subDeliveryPointsForSourcePipeId$: Observable<util.Point[]>;
  subPointsForDeliveryPipeId$: Observable<util.Point[]>;
  subSourceZonesForPipeId$: Observable<util.Zone[]>;
  sourcePipeChanged: Observable<number>;
  deliveryPipeChanged: Observable<number>;

  save$ = new Subject<util.SaveType>();
  saveResult$: Observable<number>;
  delete$ = new Subject();
  deleteResult$: Observable<object>;

  constructor() {
    // prior detail fill ins, initial values

    this.detailInitialValues = this.productDetailForm.getRawValue() as ProductDetail;

    // required data observable

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

    // detail observable trigger
    this.detail$ = this.refreshDetail$.pipe(
      switchMap(() => {
        this.productDetailLoading$.next(true);
        if (this.effectiveDate() == null) {
          const initialDetail = this.detailInitialValues
          initialDetail.meterId = this.meterId();
          initialDetail.productId = this.productId();
          return of(initialDetail);
        }
        else
          return this.service.getDetail(this.meterId(), this.productId(), this.effectiveDate());
      }),
      map(result => {
        const detail: ProductDetail = result;
        if (detail) {
          this.productName = this.getProductName(this.productId());
          util.convertToDates(detail);
          this.productDetailForm.setValue(detail);
        }
        return detail;
      }),
      tap(() => {
        this.productDetailLoading$.next(false);
      }),
      shareReplay(1),
      catchError(err => {
        return util.handleError(err, this.messageService)
      }), retry(10)
    )

    this.subSourceZonesForPipeId$ = this.refreshSourceZonesForPipe$.pipe(
      map(() => {
        const sourcePipeId: number = this.productDetailForm.get('sourcePipeId').value;
        let subZones: util.Zone[] = [];
        if (sourcePipeId) {
          subZones = this.localRequiredData.sourceZones.filter(sourceZoneItem => {
            return sourceZoneItem.pipeId === sourcePipeId;
          });
        }

        const sourceZoneId = this.productDetailForm.get('sourceZoneId').value;
        if (subZones && subZones.findIndex(x => x.zoneId === sourceZoneId) === -1)
          this.productDetailForm.patchValue({ sourceZoneId: null }, { emitEvent: false });

        if (subZones && subZones.length === 1)
          this.productDetailForm.patchValue({ sourceZoneId: subZones[0].zoneId }, { emitEvent: false });

        return subZones;

      }),
      shareReplay(1)
    )

    this.subSourcePointsForSourcePipeId$ = this.refreshSourcePointsForPipe$.pipe(
      map(() => {
        const sourcePipeId: number = this.productDetailForm.get('sourcePipeId').value;
        let pointsForPipeline: util.Point[] = [];
        if (sourcePipeId) {
          pointsForPipeline = this.localRequiredData.points.filter(sourcePointsItem => {
            return sourcePointsItem.pipeId === sourcePipeId;
          });
        }

        const pointIds: number[] = this.productDetailForm.get('sourcePointIds').value;
        if (!pointsForPipeline || !pointIds || !pointsForPipeline.some(pointItem => pointIds.some(pointId => pointId === pointItem.pointId)))
          this.productDetailForm.patchValue({ sourcePointIds: null });

        if (pointsForPipeline && pointsForPipeline.length === 1)
          this.productDetailForm.patchValue({ sourcePointIds: [pointsForPipeline[0].pointId] });

        return pointsForPipeline;
      }),
      shareReplay(1)
    )


    this.subDeliveryPointsForSourcePipeId$ = this.refreshDeliveryPointsForPipe$.pipe(
      map(() => {
        const sourcePipeId: number = this.productDetailForm.get('sourcePipeId').value;
        let pointsForPipeline: util.Point[] = [];
        if (sourcePipeId) {
          pointsForPipeline = this.localRequiredData.points.filter(sourcePointsItem => {
            return sourcePointsItem.pipeId === sourcePipeId;
          });
        }

        const pointIds: number[] = this.productDetailForm.get('deliveryPointIds').value;
        if (!pointsForPipeline || !pointIds || !pointsForPipeline.some(pointItem => pointIds.some(pointId => pointId === pointItem.pointId)))
          this.productDetailForm.patchValue({ deliveryPointIds: null });

        if (pointsForPipeline && pointsForPipeline.length === 1)
          this.productDetailForm.patchValue({ deliveryPointIds: [pointsForPipeline[0].pointId] });

        return pointsForPipeline;
      }),
      shareReplay(1)
    )

    // save and delete
    this.saveResult$ = this.save$.pipe(
      switchMap(saveType => {
        this.productDetailLoading$.next(true);
        this.productDetailForm.disable();
        const itemToSave: ProductDetail = this.productDetailForm.getRawValue();
        if (this.checkZoneAndPipeMatch(itemToSave))
          return util.handleError("A selected SourcePipe must also have a SourceZone", this.messageService);
        return this.service.saveDetail(itemToSave, saveType);
      }),
      tap(() => {
        this.productDetailLoading$.next(false);
        this.notify.success('save successful');
        this.closeDetail();
      }),
      shareReplay(1),
      catchError(err => {
        this.productDetailLoading$.next(false);
        this.productDetailForm.enable();
        return util.handleError(err, this.messageService)
      }), retry(10)
    )

    this.deleteResult$ = this.delete$.pipe(
      switchMap(() => {
        this.productDetailLoading$.next(true);
        this.productDetailForm.disable();
        return this.service.deleteDetail(this.productDetailForm.get('id').value);
      }),
      tap(() => {
        this.notify.success('delete successful');
        this.productDetailLoading$.next(false);
        this.closeDetail();
      }),
      shareReplay(1),
      catchError(err => {
        return util.handleError(err, this.messageService)
      }), retry(10)
    )

    this.sourcePipeChanged = this.productDetailForm.get('sourcePipeId').valueChanges.pipe(
      tap(() => {
        setTimeout(() => { //waits for form controls to update before calling refresh
          this.filterSourcePipes$.next(null);
          this.refreshSourceZonesForPipe$.next(null);
          this.refreshSourcePointsForPipe$.next(null);
          this.refreshDeliveryPointsForPipe$.next(null);
        });
      }),
      shareReplay(1)
    )

    // filterables for required data
    this.meterTypes$ = this.filterMeterTypes$.pipe(util.filterIdNames(this.productDetailLoading$, this.requiredData$, 'meterTypes'));
    this.sourcePipes$ = this.filterSourcePipes$.pipe(util.filterSpecials(this.productDetailLoading$, this.requiredData$.pipe(
      map(data => data.pipelines.filter(x => x.isCrudePipe || x.isGasPipe))
    ), null, 'pipeName'));

    this.sourceZones$ = this.filterSourceZones$.pipe(util.filterSpecials(this.productDetailLoading$, this.subSourceZonesForPipeId$, null, 'zoneName'));
    this.plantNames$ = this.filterPlants$.pipe(util.filterIdNames(this.productDetailLoading$, this.requiredData$, 'plantNames'));
    this.sourcePointNames$ = this.filterSourcePoints$.pipe(util.filterSpecials(this.productDetailLoading$, this.subSourcePointsForSourcePipeId$, null, 'pointName'));
    this.deliveryPointNames$ = this.filterDeliveryPoints$.pipe(util.filterSpecials(this.productDetailLoading$, this.subDeliveryPointsForSourcePipeId$, null, 'pointName'));
  }

  getProductDetailForm() {
    const fb = this.fb;
    const fg: util.FormModel<ProductDetail> = this.fb.group({
      id: fb.ctr(0, Validators.required),
      meterId: fb.ctr(0, Validators.required),
      meterTypeId: fb.ctr(0),
      productId: fb.ctr(0, Validators.required),
      number: fb.ctr(null),
      hubCode: fb.ctr(null),
      isUpstream: fb.ctr(false),
      sourcePipeId: fb.ctr(0),
      sourceZoneId: fb.ctr(0),
      sourcePointIds: fb.ctr(null, Validators.required),
      deliveryPointIds: fb.ctr(null),
      plantIds: fb.ctr(null),
      description: fb.ctr(null),
      effectiveDate: fb.ctr(util.currentDate.toDate(), Validators.required)
    });

    return fg;
  }

  save = (saveType: util.SaveType) => {
    this.productDetailForm.markAllAsTouched();
    const formsValid = this.productDetailForm.valid;
    if (formsValid)
      this.save$.next(saveType);
    else
      this.notify.error("validation failed");
  }

  delete() {
    const deleteConfirmSettings: DialogSettings = {
      title: "Please confirm",
      content: "Are you sure you want to delete this item?",
      actions: [{ text: 'No' }, { text: 'Yes', cssClass: 'k-primary' }],
      cssClass: 'utilPrompt'
    }

    this.dialogService.open(deleteConfirmSettings).result.pipe(take(1)).subscribe(result => {
      if (util.getDialogAction(result) === util.dialogAction.Yes)
        this.delete$.next(null);
    });
  }

  checkZoneAndPipeMatch(pd: ProductDetail): boolean {
    // check if there is a pipe, and if there is, check if there is a zone with it
    if (pd.sourcePipeId > 0 && (pd.sourceZoneId == null || pd.sourceZoneId == undefined))
      return true;
    return false;
  }

  closeDetail = () => {
    this.closed.emit();
  }

  onDetailClosing() {
    util.onDetailChanging(this.productDetailForm, this.dialogService, this.closeDetail, this.save);
  }

  getProductName(productId: number) {
    const products = this.localRequiredData?.products;
    if (products && productId != null)
      return products.find(p => p.id === productId).name;
    return '';
  }
}
