import { ChangeDetectorRef, Component, NgZone, OnInit } from '@angular/core';
import { DetailsFormBase } from '@objects-view/details-form-base';
import { FormGroup, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { IServiceModel, PriceOverrideModel } from '@fleio-api/billing/model/service.model';
import { Router } from '@angular/router';
import { ConfigService } from '@shared/config/config.service';
import { ServicesApiService } from '@fleio-api/billing/services/service-api.service';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { IActionResult } from '@objects-view/interfaces/actions/action-result';
import { IProductModel } from '@fleio-api/billing/model/product.model';
import { IProductCycleModel } from '@fleio-api/billing/model/product-cycle.model';
import { ProductsApiService } from '@fleio-api/billing/products/product-api.service';
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';
import { IAction } from '@objects-view/interfaces/actions/action';
import { CallbackAction } from '@objects-view/actions/callback-action';
import { MatDialog } from '@angular/material/dialog';
import { MatTableDataSource } from '@angular/material/table';
import { ConfigurableOptionsApiService } from '@fleio-api/billing/configurable-options/configurable-option-api.service';
import { TimeHelper } from '@shared/ui-api/helpers/time-helper';
import { ValidatorsHelper } from '@shared/ui-api/helpers/validators-helper';
import { IIcon } from '@shared/ui/common/interfaces/icon';
import {
  ServiceConfigurableOptionEditDialogComponent
} from '../../dialogs/service-configurable-option-edit-dialog/service-configurable-option-edit-dialog.component';

@Component({
  selector: 'app-service-edit-form',
  templateUrl: './service-edit-form.component.html',
  styleUrls: ['./service-edit-form.component.scss']
})
export class ServiceEditFormComponent extends DetailsFormBase<IServiceModel> implements OnInit {
  PriceOverrideModel = PriceOverrideModel;
  serviceForm = this.formBuilder.group({
    display_name: ['', Validators.required],
    product: ['', Validators.required],
    cycle: ['', Validators.required],
    notes: [''],
    override_suspend_until: [''],
    current_service_cycle_end: [''],
    renew_enabled: [false],
    override_suspend_until_time: [''],
    price_override: [false],
    price_override_model: [null],
    price_override_type: ['flat'],
    override_until: [null],
    override_value: [null, [ValidatorsHelper.requiredNumericValueValidator, Validators.min(0)]],
    discount_value: [null, [ValidatorsHelper.requiredNumericValueValidator, Validators.min(0)]],
    discount_percent: [null, [ValidatorsHelper.requiredNumericValueValidator, Validators.min(0)]],
    price_override_tiers: this.formBuilder.array([]),
  });
  displayedColumns: string[] = ['option', 'option_value', 'price', '(actions)'];
  dataSource;
  servicePriceDetails = null;
  productSelectedPlaceholder = $localize`Product`;
  productUnselectedPlaceholder = $localize`Select product ...`;

  optionsActions = {};
  product = this.serviceForm.controls.product;
  cycle = this.serviceForm.controls.cycle;
  price_override = this.serviceForm.controls.price_override;
  price_override_model = this.serviceForm.controls.price_override_model;
  price_override_type = this.serviceForm.controls.price_override_type;
  override_value = this.serviceForm.controls.override_value;
  discount_value = this.serviceForm.controls.discount_value;
  discount_percent = this.serviceForm.controls.discount_percent;
  price_override_tiers: UntypedFormArray = this.serviceForm.controls.price_override_tiers as UntypedFormArray;

  clearCurrentCycleEndTooltip = $localize`Clear current cycle end`;
  clearDoNotSuspendUntilTooltip = $localize`Clear do not suspend until`;
  timeHelper: TimeHelper;
  selectedCycle = null;

  overridePriceInfoIcon: IIcon = {
    name: 'info',
    verticalAlign: 'middle',
    fontSize: '20px',
    marginLeft: '5px',
    tooltip: $localize`Fill in a value to override total price of product and configurable options.\nLeave empty to automatically apply product and configurable options pricing.`
  };

  overrideUntilIcon: IIcon = {
    name: 'info',
    verticalAlign: 'middle',
    fontSize: '20px',
    marginLeft: '5px',
    tooltip: $localize`Fill in date until discount applies or leave empty for no date limit.\n Date is being adjusted upwards to cycle end date.`
  };

  discountPriceInfoIcon: IIcon = {
    name: 'info',
    verticalAlign: 'middle',
    fontSize: '20px',
    marginLeft: '5px',
    tooltip: $localize`Fill in a fixed discount value for total price of product and configurable options.`
  };

  discountPercentInfoIcon: IIcon = {
    name: 'info',
    verticalAlign: 'middle',
    fontSize: '20px',
    marginLeft: '5px',
    tooltip: $localize`Fill in a discount percent for total price of product and configurable options.`
  };

  addTierTooltip = $localize`Add tier`;
  removeTierTooltip = $localize`Remove tier`;

  private optionPriceUpdateBS: BehaviorSubject<number> = new BehaviorSubject<number>(null);
  private readonly observableOptionPriceUpdate: Observable<number> = this.optionPriceUpdateBS.asObservable();

  constructor(
    private formBuilder: UntypedFormBuilder,
    private router: Router,
    public config: ConfigService,
    private servicesApi: ServicesApiService,
    public productsApiService: ProductsApiService,
    private matDialog: MatDialog,
    private configurableOptionsApiService: ConfigurableOptionsApiService,
    ngZone: NgZone,
    changeDetectorRef: ChangeDetectorRef,
  ) {
    super(ngZone, changeDetectorRef, config);
    this.timeHelper = new TimeHelper(config);
    this.serviceForm.controls.override_suspend_until_time.setValidators(
      [ValidatorsHelper.timeValidator(this.timeHelper.timeFormat, this.timeHelper.timeExample)]
    );
  }

  productDisplay(product?: IProductModel): string | undefined {
    if (product) {
      if (product.price_model === 'free') {
        return $localize`${product.name} (free)`;
      }
      return product.name;
    } else {
      return undefined;
    }
  }

  setActions() {
    this.optionsActions = {};
    for (const configurableOption of this.object.configurable_options) {
      this.optionsActions[configurableOption.option] = this.getOptActions(configurableOption);
    }
  }

  refreshDataSource() {
    this.dataSource = new MatTableDataSource(this.object.configurable_options);
    this.setActions();
    this.updatePrice();
  }

  recalculateOptionPrices() {
    this.optionPriceUpdateBS.next(0);
  }

  getOptActions(configurableOption): IAction[] {
    const actions: IAction[] = [];

    actions.push(new CallbackAction({
      object: configurableOption,
      tooltip: $localize`Edit`,
      icon: {name: 'edit'},
      name: $localize`Edit`,
      callback: () => this.matDialog.open(
        ServiceConfigurableOptionEditDialogComponent, {
          data: {
            service: this.object,
            serviceSelectedCycle: this.selectedCycle,
            serviceConfigurableOption: configurableOption,
            previousOptions: this.dataSource.data,
            product: this.product.value,
          },
          autoFocus: false,
        }).afterClosed().pipe(map(result => {
        if (result && result.service) {
          const index = this.object.configurable_options.findIndex(
            item => item.option === configurableOption.option
          );
          this.object.configurable_options[index] = Object.assign(this.object.configurable_options[index], result);
          this.refreshDataSource();
          this.recalculateOptionPrices();
        }
        return {message: result} as IActionResult;
      }))
    }));

    return actions;
  }

  ngOnInit() {
    super.ngOnInit();
    if (this.objectController) {
      this.objectController.actionCallback = () => this.saveService();
    }
    if (this.object) {
      this.serviceForm.patchValue(this.object);
      this.cycle.patchValue(this.object.cycle.id);
      for (const price of this.object.price_override_tiers) {
        this.price_override_tiers.push(this.formBuilder.group({
          from: [price.from],
          to: [price.to],
          price: [price.price, [ValidatorsHelper.requiredNumericValueValidator, Validators.required]],
        }));
      }

      if (this.object.override_suspend_until) {
        this.serviceForm.controls.override_suspend_until.setValue(
          new Date(this.object.override_suspend_until)
        );
        this.serviceForm.controls.override_suspend_until_time.setValue(
          new Date(this.object.override_suspend_until).toLocaleTimeString(this.config.locale)
        );
      }
      this.serviceForm.controls.cycle.setValue(this.object.cycle.id);
      this.productsApiService.get(this.object.product.id).subscribe(product => {
        this.product.setValue(product);
      });
      this.refreshDataSource();
    }

    this.product.valueChanges.subscribe((product: IProductModel) => {
        if (product.requires_domain) {
          this.serviceForm.addControl(
            'domain_name',
            this.formBuilder.control(this.object.domain_name, Validators.maxLength(256))
          );
        } else {
          if (this.serviceForm.contains('domain_name')) {
            this.serviceForm.removeControl('domain_name');
          }
        }
        if (product.cycles) {
          const currentCycleId = this.cycle.value;

          if (!product.cycles.find(cycle => cycle.id === currentCycleId)) {
            if (product.cycles.length) {
              this.cycle.setValue(product.cycles[0].id);
            } else {
              this.cycle.setValue(null);
            }
          }
        }
      }
    );

    this.serviceForm.valueChanges.subscribe(() => this.updateControls());

    this.serviceForm.controls.override_until.valueChanges.pipe(
      debounceTime(500),
      distinctUntilChanged(),
    ).subscribe((newValue) => {
      if (newValue) {
        this.servicesApi.getCycleEndDT(
          this.object.id, newValue,
        ).subscribe((endDt) => {
          this.serviceForm.controls.override_until.setValue(endDt, {emitEvent: false});
        });
      }
    });

    this.observableOptionPriceUpdate.subscribe(value => {
      // in this way we re-calculate prices for percentage options if other options
      // were added or edited and this influences those options price
      if (typeof value !== 'number' || value > this.object.configurable_options.length - 1) {
        this.updatePrice();
        return;
      }
      const serviceConfigOption = this.object.configurable_options[value];
      const cycleParams = {
        option: serviceConfigOption.option,
        cycle: this.selectedCycle.cycle,
        cycle_multiplier: this.selectedCycle.cycle_multiplier,
        currency: this.object.cycle.currency,
      } as any;
      const choiceFilter = serviceConfigOption.option_value_id ? serviceConfigOption.option_value_id : null;
      if (choiceFilter === null) {
        cycleParams.value__isnull = true;
      } else {
        cycleParams.value = choiceFilter;
      }

      this.configurableOptionsApiService.getOptionPrice(
        serviceConfigOption.option,
        this.object.cycle.currency,
        JSON.stringify(cycleParams),
        serviceConfigOption.option_value,
        JSON.stringify(this.object.configurable_options),
        this.object.id,
      ).subscribe(response => {
        if (response.option_is_percentage) {
          if (typeof response.unit_price === 'number') {
            (serviceConfigOption as any).unit_price = response.unit_price;
          }
          if (serviceConfigOption.widget === 'yesno' && serviceConfigOption.option_value === 'no') {
            // yesno option with no value is set with 0 price
            serviceConfigOption.price = 0;
          } else if (typeof response.price === 'number') {
            serviceConfigOption.price = response.price;
          }
          if (typeof response.setup_fee === 'number') {
            (serviceConfigOption as any).setup_fee = response.setup_fee;
          }
        }
      }).add(() => {
        this.optionPriceUpdateBS.next(value + 1);
      });
    });
  }

  updatePrice(formValue = null) {
    // Used to show price based on current field values
    if (!formValue) {
      if (this.object.billing_type === 'prepaid') {
        formValue = this.serviceForm.value;
      }
    }

    if (!formValue || !this.serviceForm.valid) {
      return;
    }

    this.servicesApi.getNewFixedPrice(
      this.object.id,
      formValue.cycle,
      JSON.parse(JSON.stringify(this.object.configurable_options)),
      formValue.price_override,
      formValue.price_override_model,
      formValue.price_override_type,
      formValue.price_override_tiers,
      formValue.override_value,
      formValue.discount_value,
      formValue.discount_percent,
    ).subscribe(response => {
      this.servicePriceDetails = {
        price: response.price,
        currency: response.currency,
        free_of_charge: response.free_of_charge,
      };
    });
  }

  saveService(): Observable<IActionResult> {
    const value = JSON.parse(JSON.stringify(this.serviceForm.getRawValue())) as IServiceModel;
    if (value.override_suspend_until) {
      value.override_suspend_until = new Date(value.override_suspend_until);
    }
    value.status = this.object.status;
    value.product = (value.product as IProductModel).id;
    if (value.cycle instanceof Object) {
      value.cycle = (value.cycle as IProductCycleModel).id;
    }

    if (this.timeHelper.processDateFieldTime(value.override_suspend_until, this.serviceForm.controls.override_suspend_until_time).error) {
      return of(null);
    }

    value.configurable_options = JSON.parse(JSON.stringify(this.object.configurable_options));

    this.createOrUpdate(this.servicesApi, value).subscribe(() => {
      this.router.navigateByUrl(
        this.config.getPrevUrl(`billing/services/${this.object.id}`)
      ).catch(() => {
      });
    });

    return of(null);
  }

  createTier(): UntypedFormGroup {
    return this.formBuilder.group({
      from: 0,
      price: [0, [ValidatorsHelper.requiredNumericValueValidator, Validators.required, Validators.min(0)]],
      to: [{value: '∞', disabled: true}],
    });
  }

  public reCalculateTiers() {
    // if a from field changed, re-calculate all tiers to be consecutive and have only one infinite
    // eslint-disable-next-line @typescript-eslint/prefer-for-of
    for (let tierToManipulateIndex = 0; tierToManipulateIndex < this.price_override_tiers.length; tierToManipulateIndex++) {
      // eslint-disable-next-line @typescript-eslint/prefer-for-of
      const tierChanged = this.price_override_tiers.at(tierToManipulateIndex).value as {
        from: any;
        price: any;
        to: any
      };
      let nextMaxValue = 0;
      let toField = null;
      for (let tierToCompareWithIndex = 0; tierToCompareWithIndex < this.price_override_tiers.length; tierToCompareWithIndex++) {
        if (this.price_override_tiers.at(tierToCompareWithIndex).value.from > tierChanged.from &&
          (nextMaxValue > this.price_override_tiers.at(tierToCompareWithIndex).value.from || nextMaxValue === 0)) {
          nextMaxValue = this.price_override_tiers.at(tierToCompareWithIndex).value.from;
        }
      }
      if (nextMaxValue === 0 || tierToManipulateIndex === (this.price_override_tiers.length - 1)) {
        toField = '∞';
      } else {
        toField = nextMaxValue;
      }

      const tierToUpdate = this.price_override_tiers.at(tierToManipulateIndex) as FormGroup;
      tierToUpdate.controls.to.setValue(toField);
    }
  }

  public addTier(event: MouseEvent) {
    event.preventDefault();
    this.price_override_tiers.push(this.createTier());
    this.reCalculateTiers();
  }

  public removeTier(index) {
    this.price_override_tiers.removeAt(index);
    this.reCalculateTiers();
  }

  private updateControls() {
    if ([
      PriceOverrideModel.FreeService, PriceOverrideModel.OverrideValue,
      PriceOverrideModel.DiscountValue, PriceOverrideModel.DiscountPercentage,
    ].includes(this.price_override_model.value)) {
      this.price_override.setValue(true, {emitEvent: false});
    } else {
      this.price_override.setValue(false, {emitEvent: false});
    }


    if (this.price_override_type.value === 'flat') {
      switch (this.price_override_model.value) {
        case PriceOverrideModel.OverrideValue:
          this.override_value.enable({emitEvent: false});
          this.discount_value.disable({emitEvent: false});
          this.discount_percent.disable({emitEvent: false});
          break;
        case PriceOverrideModel.DiscountPercentage:
          this.discount_percent.enable({emitEvent: false});
          this.discount_value.disable({emitEvent: false});
          this.override_value.disable({emitEvent: false});
          break;
        case PriceOverrideModel.DiscountValue:
          this.discount_value.enable({emitEvent: false});
          this.override_value.disable({emitEvent: false});
          this.discount_percent.disable({emitEvent: false});
          break;
        default:
          this.discount_value.disable({emitEvent: false});
          this.override_value.disable({emitEvent: false});
          this.discount_percent.disable({emitEvent: false});
      }
    } else {
      this.discount_value.disable({emitEvent: false});
      this.override_value.disable({emitEvent: false});
      this.discount_percent.disable({emitEvent: false});
      if (!this.price_override_tiers.value.length) {
        this.price_override_tiers.push(this.createTier());
      }
    }

    const value = this.serviceForm.value
    if (value.cycle && this.product.value?.cycles) {
      this.selectedCycle = this.product.value.cycles.find(cycle => cycle.id === value.cycle);
      this.updatePrice(value);
    }
  }
}
