import { STEPPER_GLOBAL_OPTIONS } from '@angular/cdk/stepper';
import { CurrencyPipe, KeyValue } from '@angular/common';
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
import {
  AbstractControl,
  UntypedFormBuilder,
  UntypedFormControl, ValidationErrors,
  ValidatorFn,
  Validators
} from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { MatStepper } from '@angular/material/stepper';
import { marker } from '@biesbjerg/ngx-translate-extract-marker';
import { TranslateService } from '@ngx-translate/core';
import { CountryCode } from 'src/app/shared/country-codes';
import { NanoValidators } from 'src/app/shared/nano-validators';
import { PostalCodeRecord, PostalCodes } from 'src/app/shared/postal-codes';
import { PaymentService } from 'src/app/shared/server-services/payment.service';
import {
  PaymentBillings,
  PaymentBillingsData,
  PaymentBillingsRecord,
  PaymentBuyCodeRecord,
  PaymentPreapreResult,
  PaymentPrepareData,
  PaymentPrepareResultRecord,
  PaymentRecordOrder,
  PaymentShopData,
  PaymentShopDataCurrencyType,
  PaymentShopDataProduct, PaymentShopDataProductPackageMethodType,
  PaymentShopDataProductPackageTargetLimitType,
  PaymentShopDataRecord
} from 'src/app/shared/server-services/query-records/payment-records';
import { DialogService } from 'src/app/shared/services/dialog.service';
import { RouterHandler } from 'src/app/shared/services/router-handler.service';
import { TitleService } from 'src/app/shared/services/title.service';
import { States } from 'src/app/shared/states';
import { EnvironmentService } from 'src/environments/environment.service';
import { EditBillingTemplateDialogComponent } from './edit-billing-template-dialog/edit-billing-template-dialog.component';

import { AppStorage } from 'src/app/shared/app-storage';
import { AccountService } from 'src/app/shared/server-services/account.service';
import { UserRole } from 'src/app/shared/server-services/query-records/account-records';
import { CordovaService } from 'src/app/shared/services/cordova.service';
import { SnackBarService } from 'src/app/shared/services/snackbar.service';
import { sideMenuActiveObserver } from 'src/app/shared/shared-observers';
import { environment } from 'src/environments/environment';
@Component({
  selector: 'app-nano-license-buy',
  templateUrl: './nano-license-buy.component.html',
  styleUrls: ['./nano-license-buy.component.scss'],
  providers: [
    {
      provide: STEPPER_GLOBAL_OPTIONS,
      useValue: { showError: true },
    },
  ],
})
export class NanoLicenseBuyComponent implements OnInit, AfterViewInit {
  public countryCodes = CountryCode;
  public states = States;
  public postalCodes = PostalCodes;
  public filteredPostalCodes: PostalCodeRecord[] = [];
  public siteBase: string;
  public lotteryDiscountCodes: string[] = ['Cz84G2WwQQmbf', 'Jb3P67gZ4pfJv', '647FqVk3gwg4C'];

  @ViewChild('stepper', { static: false }) private stepper: MatStepper;

  shopData: PaymentShopDataRecord;
  paymentData: PaymentPrepareData;
  preparedPaymentData: PaymentPrepareResultRecord;
  paymentBillings: PaymentBillingsRecord[];
  selectedBilling: PaymentBillingsRecord;
  accountLicenseSlotLimit: number;
  licenseUrl: string = environment.site_base + '/license-buy';

  isHun: boolean = AppStorage.getItem('lang') == 'hu';
  isBundle: boolean = false;
  isCompany: boolean = false;
  isWirePriceMet: boolean = false;
  isDevOrAdmin: boolean = false;
  canPaymentBeRecurring: boolean = false;
  taxErrors: boolean = false;
  isLotteryDiscountCode: boolean = false;
  taxCharMap: any;
  taxExamples: string[];
  saveToTemplate: boolean = false;
  paymentUrl: string;
  checkoutCompleted: boolean = false;

  basketForm = this.fb.group({
    currency: [this.getCurrencyFromLang(), Validators.required],
    product: [null, Validators.required],
    package: [null, Validators.required],
    productId: [null, Validators.required],
    packageId: [null, Validators.required],
    target: ['own', Validators.required],
    number: [1, [Validators.required, NanoValidators.isWholeNumber(), Validators.min(1)]],
    sandbox_payment: [false],
  });

  billingInfoForm = this.fb.group({
    entity: ['individual', Validators.required],
    email: ['', [Validators.required, Validators.email, Validators.maxLength(255)]],
    name: ['', [Validators.required, Validators.maxLength(255)]],
    country: ['', [Validators.required]],
    state: ['', [Validators.maxLength(255)]],
    postal: ['', [Validators.required, Validators.maxLength(63)]],
    city: ['', [Validators.required, Validators.maxLength(255)]],
    address: ['', [Validators.required, Validators.maxLength(255)]],
  });

  paymentForm = this.fb.group({
    method: ['card', [Validators.required]],
    payment_recurring: [false],
    buy_code: [''],
    terms_of_service_accepted: [false, [Validators.requiredTrue]],
  });

  numberValidator: ValidatorFn;
  recurringMaxValidator: ValidatorFn;
  stateRequiredValidator: ValidatorFn = Validators.required;

  name_of_purchaser_control: UntypedFormControl = this.fb.control(null, [
    Validators.required,
    Validators.maxLength(255),
  ]);
  tax_number_control: UntypedFormControl = this.fb.control(null, [
    Validators.required,
    Validators.maxLength(63),
  ]);
  recurring_number_control: UntypedFormControl = this.fb.control(1, [
    Validators.required,
    NanoValidators.isWholeNumber(),
    Validators.min(1),
  ]);
  simple_pay_accepted_control: UntypedFormControl = this.fb.control(false, [Validators.requiredTrue]);
  lottery_accepted_control: UntypedFormControl = this.fb.control(false, [Validators.requiredTrue]);

  public isOnApp: boolean;

  constructor(
    private titleService: TitleService,
    private fb: UntypedFormBuilder,
    private paymentService: PaymentService,
    private currencyPipe: CurrencyPipe,
    private dialogService: DialogService,
    private dialog: MatDialog,
    private routerHandler: RouterHandler,
    private environmentService: EnvironmentService,
    private translate: TranslateService,
    private cordovaService: CordovaService,
    private snackbarService: SnackBarService,
    private accountService: AccountService
  ) {
    this.isOnApp = this.cordovaService.isOnApp();
    this.siteBase = this.environmentService.site_base;

    this.billingInfoForm.get('postal').valueChanges.subscribe((postal) => {
      if (this.billingInfoForm.get('country').value != 'HU') this.filteredPostalCodes = [];
      else this.filteredPostalCodes = this.filterPostalCodes(postal.toString());
    });
  }

  public openBuyLicenseInBrowser() {
    this.cordovaService.openLinkInBrowser(this.licenseUrl);
  }

  private filterPostalCodes(value: string): PostalCodeRecord[] {
    return this.postalCodes
      .filter(
        (postal) =>
          postal.city.toLowerCase().includes(value.toLowerCase()) ||
          postal.postal.toString().startsWith(value.toLowerCase()) ||
          postal.county.toLowerCase().includes(value.toLowerCase())
      )
      .slice(0, 30);
  }

  ngOnInit(): void {
    sideMenuActiveObserver.next(false);

    this.accountService.getMe().then((me) => {
      this.isDevOrAdmin =
        this.accountService.isCurrentUserInRole(UserRole.ADMIN, me) ||
        this.accountService.isCurrentUserInRole(UserRole.DEV, me);
    });

    this.paymentService.getPaymentShopData().then((shopData: PaymentShopData) => {
      this.shopData = shopData.response;
      this.accountLicenseSlotLimit = shopData.accountLicenseSlotLimit;
    });

    this.paymentService.getBuyCode().then((buyCode: PaymentBuyCodeRecord) => {
      if (buyCode.valid) {
        //buyCode.buyCode = "sad";
        this.paymentForm.get('buy_code').setValue(buyCode.buyCode);
        this.isLotteryDiscountCode = this.lotteryDiscountCodes.some(
          (lotteryCode) => lotteryCode === buyCode.buyCode
        );
        if (this.isLotteryDiscountCode) {
          this.paymentForm.addControl('lottery_accepted', this.lottery_accepted_control);
        }
      }
    });

    this.getPaymentBillings();

    this.titleService.setTabTitle(marker('Licenses'));
  }

  ngAfterViewInit(): void {
    this.stepper.selectionChange.subscribe((change) => {
      if (change.selectedIndex < change.previouslySelectedIndex) {
        //stepping backwards
      } else {
        //stepping forward
        if (change.selectedIndex == 2) {
          //prepare order
          this.prepareOrder();
        } else if (change.selectedIndex == 3) {
          //finalize form
          this.finalizeOrder();
        }
      }

      const stepId = this.stepper._getStepLabelId(change.selectedIndex);
      let stepElement = document.getElementById(stepId);
      if (stepElement) {
        setTimeout(() => {
          let errorInput = document.querySelector('.mat-form-field-invalid');
          if (errorInput) {
            stepElement = <HTMLElement>errorInput;
          }
          stepElement.scrollIntoView({ block: 'start', inline: 'nearest', behavior: 'smooth' });
        }, 250);
      }
    });
  }

  public scrollToError(): void {
    setTimeout(() => {
      let errorInput = document.querySelector('.mat-error');
      if (!errorInput) return;
      var scrollToElement = errorInput.parentElement;

      while (!scrollToElement.classList.contains('license-form-row')) {
        scrollToElement = scrollToElement.parentElement;
      }

      scrollToElement.scrollIntoView({ block: 'start', inline: 'nearest', behavior: 'smooth' });
    }, 250);
  }

  private getPaymentBillings() {
    this.paymentService.getPaymentBillings().then((paymentBillingsResponse: PaymentBillings) => {
      this.paymentBillings = paymentBillingsResponse.response;
    });
  }

  public getPrice(
    price: { [key in PaymentShopDataCurrencyType]: number },
    months: number,
    free_months: number
  ): string {
    var selectedCurrency = this.basketForm.get('currency').value as PaymentShopDataCurrencyType;
    var fullMonths = free_months ? months - free_months : months;
    var fullPrice = Math.round(price[selectedCurrency] * fullMonths);
    var pricePolicy = this.isHunCurrency ? '1.0-2' : '';
    var formattedPrice = this.currencyPipe.transform(
      fullPrice,
      selectedCurrency,
      'symbol',
      pricePolicy
    );

    return `${formattedPrice}` + this.translate.instant(marker(' + VAT'));
  }

  public getGrossPrice(
    price: { [key in PaymentShopDataCurrencyType]: number },
    months: number,
    free_months: number
  ): string {
    var selectedCurrency = this.basketForm.get('currency').value as PaymentShopDataCurrencyType;
    var fullMonths = free_months ? months - free_months : months;
    var pricePolicy = this.isHunCurrency ? '1.0-2' : '';
    var formattedPrice = this.currencyPipe.transform(
      Math.round(price[selectedCurrency] * fullMonths * 1.27),
      selectedCurrency,
      'symbol',
      pricePolicy
    );
    return `${formattedPrice} bruttó`;
  }

  public selectProductPackage(
    productId: string,
    packageId: string,
    product: PaymentShopDataProduct
  ): void {
    this.basketForm.get('productId').setValue(productId);
    this.basketForm.get('packageId').setValue(packageId);
    this.basketForm.get('product').setValue(product);

    this.updateCanBeRecurring();
    this.updateAmountValidator();
    this.updateIsWirePriceMet();
  }

  public get isHunCurrency(): boolean {
    var selectedCurrency = this.basketForm.get('currency').value as PaymentShopDataCurrencyType;
    return selectedCurrency == PaymentShopDataCurrencyType.HUF;
  }

  public onCompare(_left: KeyValue<any, any>, _right: KeyValue<any, any>): number {
    return _left.value.months - _right.value.months;
  }

  public entitySelectionChanged(value: string): void {
    if (value == 'individual') {
      this.billingInfoForm.removeControl('name_of_purchaser');
      this.billingInfoForm.removeControl('tax_number');
    } else if (value == 'company') {
      this.billingInfoForm.addControl('name_of_purchaser', this.name_of_purchaser_control);
      this.billingInfoForm.addControl('tax_number', this.tax_number_control);
    }

    this.isCompany = value == 'company';
    this.taxErrors = false;
  }

  public targetSelectionChanged(e): void {
    this.isBundle = e.value == 'bundle';
    this.updateCanBeRecurring();
    this.updateAmountValidator();
  }

  public amountChanged(e): void {
    var amountControl = this.basketForm.get('number');
    if (!amountControl.valid) return;

    this.updateIsWirePriceMet();
  }

  public countrySelectionChanged(e): void {
    var stateControl = this.billingInfoForm.get('state');
    stateControl.setValue('');

    if (e.value == 'US') {
      if (!stateControl.hasValidator(this.stateRequiredValidator))
        this.billingInfoForm.get('state').addValidators(this.stateRequiredValidator);
    } else {
      if (e.value != 'HU') {
        this.filteredPostalCodes = [];
      }

      if (stateControl.hasValidator(this.stateRequiredValidator))
        this.billingInfoForm.get('state').removeValidators(this.stateRequiredValidator);
    }

    stateControl.updateValueAndValidity();
  }

  public onZipAutocompleteChange(event): void {
    var postal: PostalCodeRecord = event.option.value;
    this.billingInfoForm.get('postal').setValue(postal.postal.toString());
    this.billingInfoForm.get('city').setValue(postal.city);
  }

  public paymentRecurringSelectionChanged(e): void {
    if (e.value == true) {
      this.paymentForm.addControl('simple_pay_accepted', this.simple_pay_accepted_control);
    } else {
      this.paymentForm.removeControl('simple_pay_accepted');
    }
  }

  public isCountryPrefixRequiredForTaxNumber(): boolean {
    return (
      this.isCompany &&
      this.shopData.constant_tax_number_prefixes.some(
        (pre) => pre == this.billingInfoForm.get('country').value
      )
    );
  }

  public preFillBillingInfo(): void {
    if (!this.selectedBilling) {
      this.billingInfoForm.reset();
      this.billingInfoForm.get('entity').setValue('individual');
      this.entitySelectionChanged('individual');
      return;
    }

    if (this.billingInfoForm.get('entity').value != this.selectedBilling.entity)
      this.entitySelectionChanged(this.selectedBilling.entity);

    for (let prop of Object.keys(this.selectedBilling)) {
      this.billingInfoForm.patchValue({ [prop]: this.selectedBilling[prop] });
    }
  }

  public openBillingsDialog(): void {
    var dialogRef = this.dialog.open(EditBillingTemplateDialogComponent, {
      data: this.paymentBillings,
    });
    dialogRef.afterClosed().subscribe((toDeleteBillings: number[]) => {
      if (toDeleteBillings) {
        var promises: Promise<number>[] = [];

        for (let toDeleteBillingId of toDeleteBillings) {
          promises.push(this.paymentService.deletePaymentBilling(toDeleteBillingId));
        }

        Promise.all(promises).then(() => {
          this.snackbarService.showSnackbar(marker('Billing template deleted!'));

          if (
            toDeleteBillings.some(
              (deleteBillingId) => this.selectedBilling?.billing_id == deleteBillingId
            )
          ) {
            //if current selected billing is deleted
            this.saveToTemplate = false;
            this.selectedBilling = null;
          }

          this.getPaymentBillings();
        });
      } else {
        this.getPaymentBillings();
      }
    });
  }

  public saveTemplate(): void {
    var paymentBillingData: PaymentBillingsData = {
      address: this.paymentData.address,
      city: this.paymentData.city,
      country: this.paymentData.country,
      email: this.paymentData.email,
      entity: this.paymentData.entity,
      name: this.paymentData.name,
      name_of_purchaser: this.paymentData.name_of_purchaser,
      postal: this.paymentData.postal,
      state: this.paymentData.state,
      tax_number: this.paymentData.tax_number,
    };

    if (this.selectedBilling) {
      this.paymentService
        .editPaymentBilling(this.selectedBilling.billing_id, paymentBillingData)
        .then((res) => {
          this.snackbarService.showSnackbar(marker('Billing template updated!'));
          this.getPaymentBillings();
          this.saveToTemplate = false;
        });
    } else {
      this.paymentService.savePaymentBilling(paymentBillingData).then((res) => {
        this.snackbarService.showSnackbar(marker('Billing data saved to templates!'));
        this.getPaymentBillings();
        this.saveToTemplate = false;
      });
    }
  }

  public prepareOrder(): void {
    this.buildPrepareOrder();

    if (this.saveToTemplate) {
      this.saveTemplate();
    }

    this.paymentService
      .preparePaymentOrder(this.paymentData)
      .then((res: PaymentPreapreResult) => {
        if (this.billingInfoForm.get('tax_number')) {
          this.taxErrors = false;
          this.billingInfoForm.get('tax_number').setErrors(null);
        }

        //this.stepper.next();
      })
      .catch((err) => {
        console.error('prepare error', err, err.error);

        if (err.error?.field == 'tax_number') {
          this.taxCharMap = err.error.example_char_map;
          this.taxExamples = err.error.examples;
          this.taxErrors = true;
          this.billingInfoForm.get('tax_number').setErrors({ taxFormatError: true });
        } else if (err.error?.field == 'email') {
          this.billingInfoForm.get('email').setErrors({ emailFormat: true });
        }

        this.stepper.selectedIndex = 1;
      });
  }

  public finalizeOrder(): void {
    this.buildPrepareOrder();

    this.paymentService
      .preparePaymentOrder(this.paymentData)
      .then((res: PaymentPreapreResult) => {
        this.preparedPaymentData = res.response as PaymentPrepareResultRecord;

        var paymentOrder: PaymentRecordOrder = {
          language: AppStorage.getItem('lang'),
          order: this.paymentData,
          orderHash: this.preparedPaymentData.order_hash,
        };

        this.paymentService
          .startPaymentOrder(paymentOrder)
          .then((res) => {
            this.paymentUrl = (<any>res).response.payment_url;
            //this.stepper.next();
          })
          .catch((err) => console.error('payment url error', err));
      })
      .catch((err) => {
        console.error('prepare error', err, err.error);

        if (err.error?.field == 'tax_number') {
          this.taxCharMap = err.error.example_char_map;
          this.taxExamples = err.error.examples;
          this.taxErrors = true;
          this.billingInfoForm.get('tax_number').setErrors({ taxFormatError: true });
        } else if (err.error?.field == 'email') {
          this.billingInfoForm.get('email').setErrors({ emailFormat: true });
        }

        this.stepper.selectedIndex = 1;
      });
  }

  public updateBuyCode(): void {
    var buyCode = this.paymentForm.get('buy_code').value;
    console.log('sending buycode', buyCode);
    this.paymentService
      .editBuyCode(buyCode)
      .then((res) => {
        this.snackbarService.showSnackbar(marker('Discount code updated!'));
        this.paymentForm.get('buy_code').markAsPristine();
        this.isLotteryDiscountCode = this.lotteryDiscountCodes.some(
          (lotteryCode) => lotteryCode === buyCode
        );
        if (this.isLotteryDiscountCode) {
          this.paymentForm.addControl('lottery_accepted', this.lottery_accepted_control);
        } else {
          this.paymentForm.removeControl('lottery_accepted');
        }
      })
      .catch((err) => {
        console.error(err);
        this.dialogService.openAlertDialog(
          marker('Invalid discount code'),
          marker('The provided discount code is not valid. Please use a correct discount code.')
        );
      });
  }

  private buildPrepareOrder(): void {
    var paymentData = {
      address: this.billingInfoForm.get('address').value,
      city: this.billingInfoForm.get('city').value,
      country: this.billingInfoForm.get('country').value,
      currency: this.basketForm.get('currency').value,
      email: this.billingInfoForm.get('email').value,
      entity: this.billingInfoForm.get('entity').value,
      method: this.paymentForm.get('method')
        ? this.paymentForm.get('method').value
        : PaymentShopDataProductPackageMethodType.CARD,
      name: this.billingInfoForm.get('name').value,
      name_of_purchaser: this.billingInfoForm.get('name_of_purchaser')?.value ?? '',
      number: parseInt(this.basketForm.get('number').value),
      package: this.basketForm.get('packageId').value,
      postal: this.billingInfoForm.get('postal').value,
      product: this.basketForm.get('productId').value,
      recurring_number:
        this.paymentForm.get('recurring_number') &&
        this.paymentForm.get('payment_recurring').value == true
          ? parseInt(this.paymentForm.get('recurring_number').value)
          : 0,
      state: this.billingInfoForm.get('state').value,
      target: this.basketForm.get('target').value,
      tax_number: this.billingInfoForm.get('tax_number')?.value ?? '',
      sandbox: this.basketForm.get('sandbox_payment').value,
    };

    if (this.isCountryPrefixRequiredForTaxNumber())
      paymentData.tax_number = paymentData.country + paymentData.tax_number;

    this.paymentData = paymentData;
  }

  private updateIsWirePriceMet() {
    var product = this.basketForm.get('product')?.value;

    if (!product) return;

    var packageId = this.basketForm.get('packageId').value;
    var productPackage = product.packages[packageId];

    if (!productPackage.wire_minimum_price) return;

    var selectedCurrency = this.basketForm.get('currency').value as PaymentShopDataCurrencyType;
    var amount = this.basketForm.get('number').value;

    var fullPrice = Math.round(product.price[selectedCurrency] * productPackage.months) * amount;
    var wire_minimum_price = productPackage.wire_minimum_price[selectedCurrency] as number;

    this.isWirePriceMet = fullPrice >= wire_minimum_price;
    if (!this.isWirePriceMet) this.paymentForm.get('method').setValue('card');
  }

  private updateAmountValidator(): void {
    var product = this.basketForm.get('product').value;
    if (!product) return;
    var packageId = this.basketForm.get('packageId').value;
    var selectedTarget = this.basketForm.get('target').value;
    var basketNumberControl = this.basketForm.get('number');
    var maxNumber = product.packages[packageId].target_limits[selectedTarget];

    if (this.numberValidator) basketNumberControl.removeValidators(this.numberValidator);

    this.numberValidator = Validators.max(maxNumber);
    basketNumberControl.addValidators(this.numberValidator);
    basketNumberControl.updateValueAndValidity();
  }

  private updateCanBeRecurring(): void {
    var product = this.basketForm.get('product').value;
    if (!product) return;
    var packageId = this.basketForm.get('packageId').value;
    var ownMayBeRecurring = product.packages[packageId].own_may_be_recurring;
    var selectedTarget = this.basketForm.get('target').value;

    this.canPaymentBeRecurring =
      ownMayBeRecurring && selectedTarget == PaymentShopDataProductPackageTargetLimitType.OWN;

    if (this.canPaymentBeRecurring) {
      this.paymentForm.addControl('recurring_number', this.recurring_number_control);
      this.recurringMaxValidator = Validators.max(this.shopData.recurring_max);
      this.recurring_number_control.addValidators(this.recurringMaxValidator);
      this.recurring_number_control.updateValueAndValidity();
    } else {
      this.paymentForm.removeControl('recurring_number');
      this.paymentForm.get('payment_recurring').setValue(false);
    }
  }

  private getCurrencyFromLang(): string {
    switch (AppStorage.getItem('lang')) {
      case 'hu':
        return 'HUF';
      case 'en':
        return 'EUR';
      default:
        return 'EUR';
    }
  }

  private taxNumberValidator(exampleArray: string[], exampleCharMap: any): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      var valid: boolean = true;

      //check lengths
      valid = exampleArray.some((sample) => sample.length == control.value.length);

      //length ok, check format
      if (valid) {
        for (let sample in exampleArray) {
          for (let i = 0; i < control.value.length; i++) {
            //TODO: check if characters match with any of the samples
          }
        }
      }

      return valid ? null : { taxNumber: { value: control.value } };
    };
  }
}
