import { HttpErrorResponse } from '@angular/common/http';
import { Component, ChangeDetectionStrategy, OnInit, ChangeDetectorRef } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
import { DomainCheckoutService } from '@domain/checkout';
import { AddressHelper, AssignedPayerHelper, CrmCustomerService, ShippingAddressHelper } from '@domain/crm';
import { HistoryComponent } from '@modal/history';
import {
  BuyerDTO,
  NotificationChannel,
  PayerDTO as CheckoutPayerDTO,
  ShippingAddressDTO as CheckoutShippingAddressDTO,
} from '@swagger/checkout';
import {
  AssignedPayerDTO,
  CustomerDTO,
  KeyValueDTOOfStringAndString,
  PayerDTO as CrmPayerDTO,
  ShippingAddressDTO as CrmShippingAddressDTO,
  ShippingAddressDTO,
} from '@swagger/crm';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { isResponseArgs } from '@utils/object';
import { isBoolean } from 'lodash';
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
import { catchError, first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { CantAddCustomerToCartModalComponent } from '../modals/cant-add-customer-to-cart-modal/cant-add-customer-to-cart.component';
import { CantAddCustomerToCartData } from '../modals/cant-add-customer-to-cart-modal/cant-add-customer-to-cart.data';
import { CantSelectGuestModalComponent } from '../modals/cant-select-guest/cant-select-guest-modal.component';

@Component({
  selector: 'page-customer-details',
  templateUrl: 'customer-details.component.html',
  styleUrls: ['customer-details.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerDetailsComponent implements OnInit {
  customerId$: Observable<number>;
  customer$: Observable<CustomerDTO>;

  assignedPayers$: Observable<AssignedPayerDTO[]>;

  shippingAddresses$: Observable<ShippingAddressDTO[]>;

  customerFeatures$: Observable<KeyValueDTOOfStringAndString[]>;
  customerType$: Observable<string>;

  canAdd$: Observable<boolean>;
  canEdit$: Observable<boolean>;

  selectedPayer?: number = undefined;
  selectedAddress?: number = undefined;

  isB2b$: Observable<boolean>;
  isWebshopOrGuest$: Observable<boolean>;
  cartExists$: Observable<boolean>;
  hasCustomerCard$: Observable<boolean>;

  fetchShippingAdresses$ = new BehaviorSubject<boolean>(false);
  fetchAssignedPayers$ = new BehaviorSubject<boolean>(false);
  showSpinner: boolean;

  continueDisabled$ = combineLatest([this.fetchShippingAdresses$, this.fetchAssignedPayers$]).pipe(
    map(([fetchShippingAdresses, fetchAssignedPayers]) => fetchShippingAdresses || fetchAssignedPayers || this.showSpinner)
  );

  private currentBreadcrumb: Breadcrumb;

  constructor(
    private activatedRoute: ActivatedRoute,
    private customerDetailsService: CrmCustomerService,
    private breadcrumb: BreadcrumbService,
    public application: ApplicationService,
    private checkoutService: DomainCheckoutService,
    private router: Router,
    private modal: UiModalService,
    private cdr: ChangeDetectorRef
  ) {}

  ngOnInit() {
    this.cartExists$ = this.checkoutService
      .getShoppingCart({
        processId: this.application.activatedProcessId,
      })
      .pipe(
        map((cart) => cart.items || []),
        map((items) => {
          return items.length > 0;
        })
      );

    this.customerId$ = this.activatedRoute.params.pipe(map((params) => Number(params['customerId'])));

    this.customer$ = this.customerId$.pipe(
      switchMap((customerId) => this.customerDetailsService.getCustomer(customerId, 1)),
      map(({ result }) => result),
      map((customer) => {
        this.updateBreadcrumbName(customer);
        return customer;
      }),
      shareReplay()
    );

    this.assignedPayers$ = this.customer$.pipe(
      tap(() => this.fetchAssignedPayers$.next(true)),
      switchMap((customer) =>
        this.customerDetailsService.getAssignedPayers({ customerId: customer?.id, take: 50 }).pipe(
          map((res) => {
            const defaultPayer = AssignedPayerHelper.getDefaultAssignedPayer(res.result);
            this.selectedPayer = defaultPayer?.payer?.id;

            if (
              !AddressHelper.hasRequiredProperties(customer?.address) ||
              customer.features?.some((f) => f.key === 'webshop' || f.key === 'guest')
            ) {
              if (!this.selectedPayer && res.result.length > 0) {
                this.selectedPayer = res.result[0].payer?.id;
              }
            }

            this.cdr.markForCheck();
            return res.result;
          })
        )
      ),
      tap(() => this.fetchAssignedPayers$.next(false)),
      shareReplay()
    );

    this.shippingAddresses$ = this.customer$.pipe(
      tap(() => this.fetchShippingAdresses$.next(true)),
      switchMap((customer) =>
        this.customerDetailsService.getShippingAddresses({ customerId: customer.id, take: 50 }).pipe(
          map((res) => {
            const defaultShippingAddress = ShippingAddressHelper.getDefaultShippingAddress(res.result);

            this.selectedAddress = defaultShippingAddress?.id;

            if (
              !AddressHelper.hasRequiredProperties(customer?.address) ||
              customer.features?.some((f) => f.key === 'webshop' || f.key === 'guest')
            ) {
              if (!this.selectedAddress && res.result.length > 0) {
                this.selectedAddress = res.result[0].id;
              }
            }

            this.cdr.markForCheck();

            return res.result;
          })
        )
      ),
      tap(() => this.fetchShippingAdresses$.next(false)),
      shareReplay()
    );

    this.customerFeatures$ = this.customer$.pipe(map((customer: CustomerDTO) => Object.values(customer.features).filter((f) => f.enabled)));
    this.hasCustomerCard$ = this.customerFeatures$.pipe(
      map((features) => !!features.find((feature) => feature.description === 'Kundenkarte'))
    );
    this.customerType$ = this.customerFeatures$.pipe(
      map(
        (features: KeyValueDTOOfStringAndString[]) =>
          features.find((f) => f.key === 'store' || f.key === 'webshop' || f.key === 'b2b' || f.key === 'guest')?.key
      )
    );

    this.isB2b$ = this.customerType$.pipe(map((type) => type === 'b2b'));
    this.isWebshopOrGuest$ = this.customerType$.pipe(map((type) => type === 'webshop' || type === 'guest'));

    this.canAdd$ = this.customerFeatures$.pipe(map((features) => !features.find((f) => f.key === 'guest')));
    this.canEdit$ = this.customerFeatures$.pipe(map((features) => !features.find((f) => f.key === 'guest' || f.key === 'webshop')));

    this.createBreadcrumb();
  }

  async createBreadcrumb() {
    const customerId = await this.customerId$.pipe(first()).toPromise();

    this.currentBreadcrumb = await this.breadcrumb.addBreadcrumbIfNotExists({
      key: this.application.activatedProcessId,
      name: 'Kundendetails',
      path: `/kunde/${this.application.activatedProcessId}/customer/${customerId}`,
      tags: ['customer', 'details'],
      section: 'customer',
    });

    this.breadcrumb.removeBreadcrumbsAfter(this.currentBreadcrumb.id, ['customer']);
  }

  updateBreadcrumbName(customer: CustomerDTO) {
    const isB2b = customer.features.some((s) => s.key === 'b2b');

    const name: string[] = [];

    if (isB2b && customer?.organisation?.name) {
      name.push(customer.organisation.name);
    }

    if (name.length === 0) {
      name.push(customer.firstName);
      name.push(customer.lastName);
    }

    this.breadcrumb.patchBreadcrumb(this.currentBreadcrumb.id, { name: name.filter((n) => !!n).join(' ') });
  }

  async openHistory() {
    const customer = await this.customer$.pipe(first()).toPromise();
    this.modal.open({
      content: HistoryComponent,
      title: 'Historie',
      config: { showScrollbarY: false },
      data: {
        mode: 'customer',
        item: customer,
      },
    });
  }

  async continue() {
    this.showSpinner = true;
    const customer = await this.customer$.pipe(first()).toPromise();

    const shippingAddress = await this.getSelectedShippingAddress();

    // Check if customer can be added to the checkout
    const canSetCustomer = await this.checkoutService
      .canSetCustomer({ processId: this.application.activatedProcessId, customerFeatures: this.getCusomterFeatures(customer) })
      .toPromise();
    if (!canSetCustomer.ok) {
      let canBeExtendedTo = await this.customerDetailsService.canUpgrade(customer.id).toPromise();

      this.modal.open({
        content: CantAddCustomerToCartModalComponent,
        data: {
          message: canSetCustomer.message,
          customerId: customer.id,
          required: canSetCustomer.create,
          upgradeableTo: canBeExtendedTo,
        } as CantAddCustomerToCartData,
      });
      this.showSpinner = false;
      return;
    } else {
      if (shippingAddress) {
        const canAddDestination = await this.checkoutService
          .canAddDestination({
            processId: this.application.activatedProcessId,
            destinationDTO: {
              target: 2,
              shippingAddress: shippingAddress.data,
            },
          })
          .pipe(
            catchError((err) => {
              if (err instanceof HttpErrorResponse && isResponseArgs(err.error)) {
                return of(Object.values(err.error.invalidProperties).join('; ') || err.error.message || err.message);
              }
              return of(err.message);
            })
          )
          .toPromise();

        if (!isBoolean(canAddDestination)) {
          this.modal.open({
            content: UiMessageModalComponent,
            title: 'Warenkorb kann dem Kunden nicht zugewiesen werden',
            data: {
              message: canAddDestination,
            },
          });
          this.showSpinner = false;
          this.cdr.markForCheck();
          return;
        }
      }

      const updateDestination = await this.checkoutService
        .setDestinationForCustomer({
          processId: this.application.activatedProcessId,
          customerFeatures: this.getCusomterFeatures(customer),
        })
        .toPromise();

      if (!updateDestination.ok) {
        this.modal.open({
          content: CantAddCustomerToCartModalComponent,
          data: {
            message: canSetCustomer.message,
            customerId: customer.id,
            required: canSetCustomer.create,
            upgradeableTo: undefined,
          } as CantAddCustomerToCartData,
        });

        this.showSpinner = false;
        this.cdr.markForCheck();
        return;
      }
    }

    const isGuest = customer.features.some((s) => s.key === 'guest');
    if (isGuest && customer.orderCount > 0) {
      this.modal.open({
        content: CantSelectGuestModalComponent,
        data: customer,
      });
      this.showSpinner = false;
      this.cdr.markForCheck();
      return;
    }

    // Set Process Name
    const isB2b = customer.features.some((s) => s.key === 'b2b');

    this.application.patchProcess(this.application.activatedProcessId, {
      name: isB2b ? (customer.organisation?.name ? customer.organisation?.name : customer.lastName) : customer.lastName,
    });

    const currentBuyer = await this.checkoutService.getBuyer({ processId: this.application.activatedProcessId }).pipe(first()).toPromise();

    // Set Buyer For Process
    this.checkoutService.setBuyer({
      processId: this.application.activatedProcessId,
      buyer: this.getCheckoutBuyerDtoFromCrmCustomerDto(customer),
    });

    this.checkoutService.setCustomerFeatures({
      processId: this.application.activatedProcessId,
      customerFeatures: this.getCusomterFeatures(customer),
    });

    if (currentBuyer?.buyerNumber !== customer.customerNumber) {
      this.checkoutService.setNotificationChannels({
        processId: this.application.activatedProcessId,
        notificationChannels: customer.notificationChannels === (3 as NotificationChannel) ? 1 : customer.notificationChannels,
      });
    }

    // Set Invoice Address If Selected
    const payer = await this.getSelectedPayerAddress();
    if (payer) {
      this.checkoutService.setPayer({
        processId: this.application.activatedProcessId,
        payer,
      });
    }

    // Set Shipping Address Address If Selected
    if (shippingAddress) {
      this.checkoutService.setShippingAddress({
        processId: this.application.activatedProcessId,
        shippingAddress: shippingAddress.data,
      });
    }

    // Navigate To Catalog Or Cart
    if (await this.cartExists$.pipe(first()).toPromise()) {
      this.router.navigate(['/kunde', this.application.activatedProcessId, 'cart', 'review']);
    } else {
      this.router.navigate(['/kunde', this.application.activatedProcessId, 'product', 'search']);
    }

    this.showSpinner = false;
  }

  async getSelectedShippingAddress() {
    try {
      if (!!this.selectedAddress) {
        const shippingAddresses = await this.shippingAddresses$.pipe(first()).toPromise();
        const shippingAddress = shippingAddresses?.find((s) => s.id === this.selectedAddress);
        if (shippingAddress) {
          return {
            source: 'shippingAddress',
            data: this.getCheckoutShippingAddressDtoFromCrmShippingAddressDTO(shippingAddress),
          };
        }
      } else {
        const customer = await this.customer$.pipe(first()).toPromise();
        return {
          source: 'customer',
          data: this.getCheckoutShippingAddressDtoFromCrmCustomerDto(customer),
        };
      }
    } catch (error) {}
  }

  async getSelectedPayerAddress() {
    try {
      if (!!this.selectedPayer) {
        const assignedPayers = await this.assignedPayers$.pipe(first()).toPromise();
        const payer = assignedPayers.find((f) => f.payer.id === this.selectedPayer)?.payer?.data;
        return this.getCheckoutPayerDtoFromCrmPayerDto(payer);
      } else {
        const customer = await this.customer$.pipe(first()).toPromise();
        return this.getCheckoutPayerDtoFromCrmCustomerDto(customer);
      }
    } catch (error) {}
  }

  getCusomterFeatures(custoemr: CustomerDTO): { [key: string]: string } {
    const customerFeatures = custoemr.features;
    const features: { [key: string]: string } = {};

    for (const feature of customerFeatures) {
      features[feature.key] = feature.key;
    }

    return features;
  }

  getCheckoutBuyerDtoFromCrmCustomerDto(customer: CustomerDTO): BuyerDTO {
    return {
      source: customer.id,
      reference: { id: customer.id },
      buyerType: customer.customerType as any,
      buyerNumber: customer.customerNumber,
      gender: customer.gender,
      title: customer.title,
      firstName: customer.firstName,
      lastName: customer.lastName,
      dateOfBirth: customer.dateOfBirth,
      communicationDetails: customer.communicationDetails ? { ...customer.communicationDetails } : undefined,
      organisation: customer.organisation ? { ...customer.organisation } : undefined,
      address: customer.address ? { ...customer.address } : undefined,
    };
  }

  getCheckoutPayerDtoFromCrmCustomerDto(customer: CustomerDTO): CheckoutPayerDTO {
    return {
      reference: { id: customer.id },
      payerType: customer.customerType as any,
      payerNumber: customer.customerNumber,
      payerStatus: 0,
      gender: customer.gender,
      title: customer.title,
      firstName: customer.firstName,
      lastName: customer.lastName,
      dateOfBirth: customer.dateOfBirth,
      communicationDetails: customer.communicationDetails ? { ...customer.communicationDetails } : undefined,
      organisation: customer.organisation ? { ...customer.organisation } : undefined,
      address: customer.address ? { ...customer.address } : undefined,
    };
  }

  getCheckoutPayerDtoFromCrmPayerDto(payer: CrmPayerDTO): CheckoutPayerDTO {
    return {
      reference: { id: payer.id },
      payerType: payer.payerType as any,
      payerNumber: payer.payerNumber,
      payerStatus: payer.payerStatus,
      gender: payer.gender,
      title: payer.title,
      firstName: payer.firstName,
      lastName: payer.lastName,
      communicationDetails: payer.communicationDetails ? { ...payer.communicationDetails } : undefined,
      organisation: payer.organisation ? { ...payer.organisation } : undefined,
      address: payer.address ? { ...payer.address } : undefined,
      source: payer.id,
    };
  }

  getCheckoutShippingAddressDtoFromCrmCustomerDto(customer: CustomerDTO): CheckoutShippingAddressDTO {
    return {
      reference: { id: customer.id },
      gender: customer?.gender,
      title: customer?.title,
      firstName: customer?.firstName,
      lastName: customer?.lastName,
      communicationDetails: customer?.communicationDetails ? { ...customer?.communicationDetails } : undefined,
      organisation: customer?.organisation ? { ...customer?.organisation } : undefined,
      address: customer?.address ? { ...customer?.address } : undefined,
    };
  }

  getCheckoutShippingAddressDtoFromCrmShippingAddressDTO(address: CrmShippingAddressDTO): CheckoutShippingAddressDTO {
    return {
      reference: { id: address.id },
      gender: address.gender,
      title: address.title,
      firstName: address.firstName,
      lastName: address.lastName,
      communicationDetails: address.communicationDetails ? { ...address.communicationDetails } : undefined,
      organisation: address.organisation ? { ...address.organisation } : undefined,
      address: address.address ? { ...address.address } : undefined,
      source: address.id,
    };
  }
}
