import { Injectable } from '@angular/core';
import {
  AddressDTO,
  AssignedPayerDTO,
  AutocompleteDTO,
  CommunicationDetailsDTO,
  CountryDTO,
  CustomerDTO,
  CustomerInfoDTO,
  CustomerService,
  InputDTO,
  ListResponseArgsOfCustomerInfoDTO,
  NotificationChannel,
  PayerDTO,
  PayerService,
  ResponseArgsOfHistoryDTO,
  ResponseArgsOfIEnumerableOfBonusCardInfoDTO,
  ShippingAddressDTO,
} from '@swagger/crm';
import { isArray } from '@utils/common';
import { PagedResult, Result } from 'apps/domain/defs/src/public-api';
import { Observable, of, ReplaySubject } from 'rxjs';
import { catchError, map, mergeMap, retry } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class CrmCustomerService {
  constructor(private customerService: CustomerService, private payerService: PayerService) {}

  complete(queryString: string, filter?: { [key: string]: string }): Observable<Result<AutocompleteDTO[]>> {
    return this.customerService.CustomerCustomerAutocomplete({
      input: queryString,
      filter: filter || {},
      take: 5,
    });
  }

  canUpgrade(customerId: number): Observable<InputDTO> {
    return this.customerService
      .CustomerCanExtendCustomer({
        customerId,
      })
      .pipe(map((res) => res.result));
  }

  getCustomers(
    queryString: string,
    options: { take?: number; skip?: number; filter?: { [key: string]: string } } = { take: 20, skip: 0 }
  ): Observable<ListResponseArgsOfCustomerInfoDTO> {
    return this.customerService.CustomerListCustomers({
      input: !!queryString ? { qs: queryString } : undefined,
      take: options.take,
      skip: options.skip,
      filter: options.filter || {},
    });
  }

  getCustomersByCustomerCardNumber(queryString: string): Observable<PagedResult<CustomerInfoDTO>> {
    return this.customerService.CustomerGetCustomerByBonuscard(!!queryString ? queryString : undefined);
  }

  getCustomer(customerId: number, eagerLoading?: number): Observable<Result<CustomerDTO>> {
    return this.customerService.CustomerGetCustomer({ customerId, eagerLoading });
  }

  getAssignedPayers(params: CustomerService.CustomerGetAssignedPayersByCustomerIdParams) {
    return this.customerService.CustomerGetAssignedPayersByCustomerId(params);
  }

  getFilters(): Observable<Result<InputDTO[]>> {
    return this.customerService.CustomerQueryCustomerFilter();
  }

  /* @internal */
  getNotificationChannelForCommunicationDetails({
    communicationDetails,
  }: {
    communicationDetails: CommunicationDetailsDTO;
  }): NotificationChannel {
    let notificationChannels: number = 0;

    if (communicationDetails?.email) {
      notificationChannels += 1;
    }
    if (communicationDetails?.mobile) {
      notificationChannels += 2;
    }

    return notificationChannels as NotificationChannel;
  }

  patchCustomer(customerId: number, customer: CustomerDTO): Observable<Result<CustomerDTO>> {
    const notificationChannels = this.getNotificationChannelForCommunicationDetails({
      communicationDetails: customer?.communicationDetails,
    });

    return this.customerService.CustomerPatchCustomer({ customerId, customer: { ...customer, notificationChannels } });
  }

  createB2BCustomer(customer: CustomerDTO) {
    const notificationChannels = this.getNotificationChannelForCommunicationDetails({
      communicationDetails: customer?.communicationDetails,
    });

    return this.customerService.CustomerCreateCustomer({ ...customer, customerType: 16, notificationChannels });
  }

  createOnlineCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
    const payload: CustomerDTO = { ...customer, customerType: 8, hasOnlineAccount: true };

    const notificationChannels = this.getNotificationChannelForCommunicationDetails({
      communicationDetails: customer?.communicationDetails,
    });

    if (!(isArray(payload.shippingAddresses) && payload.shippingAddresses.length > 0)) {
      payload.shippingAddresses = [
        {
          data: {
            address: payload.address,
            communicationDetails: payload.communicationDetails,
            firstName: payload.firstName,
            gender: payload.gender,
            lastName: payload.lastName,
            organisation: payload.organisation,
            title: payload.title,
            type: 1,
            isDefault: new Date().toISOString(),
          },
        },
      ];
    }

    if (!(isArray(payload.payers) && payload.payers.length > 0)) {
      payload.payers = [
        {
          payer: {
            data: {
              address: payload.address,
              communicationDetails: payload.communicationDetails,
              firstName: payload.firstName,
              gender: payload.gender,
              lastName: payload.lastName,
              organisation: payload.organisation,
              title: payload.title,
              payerType: payload.customerType,
            },
          },
        },
      ];
    }

    return this.customerService.CustomerCreateOnlineCustomer({ ...payload, notificationChannels });
  }

  createGuestCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
    const notificationChannels = this.getNotificationChannelForCommunicationDetails({
      communicationDetails: customer?.communicationDetails,
    });
    const payload: CustomerDTO = { ...customer, customerType: 8, isGuestAccount: true, notificationChannels };

    if (!(isArray(payload.shippingAddresses) && payload.shippingAddresses.length > 0)) {
      payload.shippingAddresses = [
        {
          data: {
            address: payload.address,
            communicationDetails: payload.communicationDetails,
            firstName: payload.firstName,
            gender: payload.gender,
            lastName: payload.lastName,
            organisation: payload.organisation,
            title: payload.title,
            type: 1,
          },
        },
      ];
    }

    if (!(isArray(payload.payers) && payload.payers.length > 0)) {
      payload.payers = [
        {
          payer: {
            data: {
              address: payload.address,
              communicationDetails: payload.communicationDetails,
              firstName: payload.firstName,
              gender: payload.gender,
              lastName: payload.lastName,
              organisation: payload.organisation,
              title: payload.title,
              payerType: payload.customerType,
            },
          },
        },
      ];
    }

    return this.customerService.CustomerCreateOnlineCustomer(payload);
  }

  createBranchCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
    const notificationChannels = this.getNotificationChannelForCommunicationDetails({
      communicationDetails: customer?.communicationDetails,
    });
    return this.customerService.CustomerCreateCustomer({ ...customer, customerType: 8, notificationChannels });
  }

  validateAddress(address: AddressDTO): Observable<Result<AddressDTO[]>> {
    return this.customerService.CustomerValidateAddress(address);
  }

  private cachedCountriesFailed = false;
  private cachedCountries: ReplaySubject<Result<CountryDTO[]>>;
  getCountries(): Observable<Result<CountryDTO[]>> {
    if (!this.cachedCountries || this.cachedCountriesFailed) {
      this.cachedCountriesFailed = false;
      this.cachedCountries = new ReplaySubject();
      this.customerService
        .CustomerGetCountries({})
        .pipe(
          retry(3),
          catchError((err) => {
            this.cachedCountriesFailed = true;
            return of<Result<CountryDTO[]>>({
              error: true,
              message: err?.message,
            });
          })
        )
        .subscribe(this.cachedCountries);
    }

    return this.cachedCountries.asObservable();
  }

  emailExists(email: string): Observable<Result<boolean>> {
    return this.customerService.CustomerEmailExists(email);
  }

  createPayer(customerId: number, payer: PayerDTO, isDefault?: boolean): Observable<[Result<PayerDTO>, Result<AssignedPayerDTO>]> {
    return this.getCustomer(customerId).pipe(
      mergeMap((customerResponse) =>
        this.payerService
          .PayerCreatePayer({ ...payer, payerType: customerResponse.result.customerType })
          .pipe(
            mergeMap((payerResponse) =>
              this.customerService
                .CustomerAddPayerReference({ customerId: customerId, payerId: payerResponse.result.id, isDefault: isDefault })
                .pipe(
                  map((assigendPayerResponse) => [payerResponse, assigendPayerResponse] as [Result<PayerDTO>, Result<AssignedPayerDTO>])
                )
            )
          )
      )
    );
  }

  updatePayer(customerId: number, payerId: number, payer: PayerDTO, isDefault?: boolean): Observable<Result<PayerDTO>> {
    return this.payerService
      .PayerUpdatePayer({ payerId, payer })
      .pipe(mergeMap((response) => this.setDefaultPayer(payerId, customerId, isDefault).pipe(map(() => response))));
  }

  setDefaultPayer(payerId: number, customerId: number, isDefault = true): Observable<Result<AssignedPayerDTO>> {
    return this.customerService.CustomerModifyPayerReference({ payerId, customerId, isDefault });
  }

  createShippingAddress(
    customerId: number,
    shippingAddress: ShippingAddressDTO,
    isDefault?: boolean
  ): Observable<Result<ShippingAddressDTO>> {
    const data: ShippingAddressDTO = { ...shippingAddress };
    if (isDefault) {
      data.isDefault = new Date().toJSON();
    } else {
      delete data.isDefault;
    }

    return this.customerService.CustomerCreateShippingAddress({ customerId, shippingAddress: data });
  }

  updateShippingAddress(
    customerId: number,
    shippingAddressId: number,
    shippingAddress: ShippingAddressDTO,
    isDefault?: boolean
  ): Observable<Result<ShippingAddressDTO>> {
    const data: ShippingAddressDTO = { ...shippingAddress };

    if (isDefault) {
      data.isDefault = new Date().toJSON();
    } else {
      delete data.isDefault;
    }

    return this.customerService.CustomerUpdateShippingAddress({ shippingAddressId, shippingAddress: data, customerId });
  }

  getShippingAddress(shippingAddressId: number): Observable<Result<ShippingAddressDTO>> {
    return this.customerService.CustomerGetShippingaddress(shippingAddressId);
  }

  getShippingAddresses(params: CustomerService.CustomerGetShippingAddressesParams): Observable<Result<ShippingAddressDTO[]>> {
    return this.customerService.CustomerGetShippingAddresses(params);
  }

  getPayer(payerId: number): Observable<Result<PayerDTO>> {
    return this.payerService.PayerGetPayer(payerId);
  }

  getCustomerCard(customerId: number): Observable<ResponseArgsOfIEnumerableOfBonusCardInfoDTO> {
    return this.customerService.CustomerGetBonuscards(customerId);
  }

  getCustomerHistory(customerId: number): Observable<ResponseArgsOfHistoryDTO> {
    return this.customerService.CustomerGetCustomerHistory({ customerId });
  }
}
