import { Injectable } from '@angular/core';
import { QueryTokenDTO } from '@swagger/cat';
import {
  CustomerService as CustomerApiService,
  PayerService,
} from '@swagger/crm';
import { map, switchMap, catchError } from 'rxjs/operators';
import { Observable, of, combineLatest, BehaviorSubject } from 'rxjs';
import { CustomerMapping } from '../mappings/customer.mapping';
import { CustomerSearchResponse } from '../models/customer-search-response.model';
import { User, Address } from '../models/user.model';
import {
  CustomerDTO,
  AddressDTO,
  ResponseArgsOfCustomerDTO,
  ResponseArgsOfBoolean,
  ListResponseArgsOfCustomerInfoDTO,
  ShippingAddressDTO,
  PayerDTO,
  ResponseArgsOfIEnumerableOfCustomerInfoDTO,
  CustomerInfoDTO,
} from '@swagger/crm';

import { isNullOrUndefined } from 'util';
import { ResponseArgsOfPayerDTO } from '@swagger/oms';
import { Country } from '../models/country.model';
import { CustomerFilters } from '../../modules/customer';

@Injectable({
  providedIn: 'root',
})
export class CustomerService {
  addAddressError$ = new BehaviorSubject<any>(null);
  constructor(
    private customerService: CustomerApiService,
    private mapper: CustomerMapping,
    private payerService: PayerService
  ) {}

  createCustomer(_customer: User): Observable<User> {
    const customer: CustomerDTO = this.mapper.fromUser(_customer);
    return this.customerService.CustomerCreateCustomer(customer).pipe(
      map((response: ResponseArgsOfCustomerDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }
        return this.mapper.customerDTOtoUser(response.result);
      })
    );
  }

  createCustomerOnline(_customer: User): Observable<User> {
    const customer: CustomerDTO = this.mapper.fromUser(_customer);
    return this.customerService.CustomerCreateOnlineCustomer(customer).pipe(
      map((response: ResponseArgsOfCustomerDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }
        return this.mapper.customerDTOtoUser(response.result);
      })
    );
  }

  updateCustomer(user: User): Observable<CustomerDTO> {
    const customer: CustomerDTO = this.mapper.fromUserSimple(user);
    const params: CustomerApiService.CustomerUpdateCustomerParams = {
      customer: customer,
      customerId: customer.id,
    };

    return this.customerService.CustomerUpdateCustomer(params).pipe(
      map((response: ResponseArgsOfCustomerDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }

  deleteCustomer(customerId: number): Observable<boolean> {
    const params: CustomerApiService.CustomerDeleteCustomerParams = {
      customerId,
    };

    return this.customerService.CustomerDeleteCustomer(params).pipe(
      map((response: ResponseArgsOfBoolean) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }

  searchCustomer(
    searchTerm: string,
    fuzzy = false,
    skip = 0,
    take = 0,
    hitsOnly = false,
    filter?: CustomerFilters
  ): Observable<CustomerSearchResponse> {
    const noFilters = filter
      ? isNullOrUndefined(filter.bonuscard) &&
        isNullOrUndefined(filter.guestaccount) &&
        isNullOrUndefined(filter.onlineshop)
        ? true
        : false
      : false;
    const params: QueryTokenDTO = {
      input: {
        qs: searchTerm,
      },
      fuzzy: fuzzy ? 1 : 0,
      skip: skip,
      take: take,
      hitsOnly: hitsOnly,
      filter:
        filter && !noFilters
          ? {
              ['bonuscard']: !isNullOrUndefined(filter.bonuscard)
                ? String(filter.bonuscard)
                : undefined,
              ['guestaccount']: !isNullOrUndefined(filter.guestaccount)
                ? String(filter.guestaccount)
                : undefined,
              ['onlineshop']: !isNullOrUndefined(filter.onlineshop)
                ? String(filter.onlineshop)
                : undefined,
            }
          : undefined,
    };

    return this.customerService.CustomerListCustomers(params).pipe(
      map((response: ListResponseArgsOfCustomerInfoDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }
        return {
          customers: response.result.map((t) =>
            this.mapper.customerInfoDTOtoUser(t)
          ),
          hits: response.hits,
          message: response.message,
        };
      })
    );
  }

  getCustomer(
    customer$: Observable<ResponseArgsOfCustomerDTO>
  ): Observable<User> {
    return customer$.pipe(
      map((response: ResponseArgsOfCustomerDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }
        return {
          shippingAddresses: response.result.shippingAddresses.map(
            (addressEnity) => addressEnity.id
          ),
          user: this.mapper.customerDTOtoUser(response.result),
        };
      }),
      switchMap((customerResult) => {
        const user = customerResult.user;
        if (
          !customerResult.shippingAddresses ||
          customerResult.shippingAddresses.length === 0
        ) {
          return of(customerResult.user);
        }
        const observablesOfShippingAddresses = customerResult.shippingAddresses.map(
          (t) => {
            return this.customerService.CustomerGetShippingaddress(t).pipe(
              map((response) => {
                if (response.error) {
                  throw new Error(response.message);
                }
                return response.result;
              })
            );
          }
        );
        const shippingAddresses$ = combineLatest(
          observablesOfShippingAddresses
        );
        return shippingAddresses$.pipe(
          switchMap((address: ShippingAddressDTO[]) => {
            const updatedUser = user;
            if (!!address) {
              const addresses = address.map((t) => {
                return {
                  ...this.mapper.fromShippingAddressDtoToAddress(t),
                  synced: true,
                };
              });
              const defaultAddress = addresses.reduce((ad1, ad2) => {
                if (ad1.defaultSince >= ad2.defaultSince) {
                  return ad1;
                }
                return ad2;
              });
              updatedUser.delivery_addres = defaultAddress;
              updatedUser.poossible_delivery_addresses = addresses;
            }
            return of(updatedUser);
          })
        );
      }),
      switchMap((customer: User) => {
        const updateCustomer = customer;
        return this.customerService
          .CustomerGetAssignedPayersByCustomerId(customer.id)
          .pipe(
            map((response) => {
              if (response.error) {
                throw new Error(response.message);
              }
              return { customer: customer, assignedPayers: response.result };
            }),
            switchMap((response) => {
              if (
                Array.isArray(response.assignedPayers) &&
                response.assignedPayers.length > 0
              ) {
                const invoiceAddresses = response.assignedPayers.map((t) => {
                  return {
                    ...this.mapper.fromAssignedPayerDtoToAddress(t),
                    synced: true,
                  };
                });
                const defaultAddress = invoiceAddresses.reduce((ad1, ad2) => {
                  if (ad1.defaultSince >= ad2.defaultSince) {
                    return ad1;
                  }
                  return ad2;
                });
                updateCustomer.poossible_invoice_addresses = invoiceAddresses;
                updateCustomer.invoice_address = defaultAddress;
              }

              return of(updateCustomer);
            })
          );
      })
    );
  }

  getCustomerById(customerId: number): Observable<User> {
    const params = {
      customerId: customerId,
    };
    return this.getCustomer(this.customerService.CustomerGetCustomer(params));
  }

  scannCustomer(input: string): Observable<CustomerSearchResponse> {
    return this.customerService.CustomerGetCustomerByBonuscard(input).pipe(
      map((response: ResponseArgsOfIEnumerableOfCustomerInfoDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }
        return {
          customers: response.result.map((t) =>
            this.mapper.customerInfoDTOtoUser(t)
          ),
          hits: 0,
        };
      })
    );
  }

  validateScannedCustomer(input: string): Observable<CustomerInfoDTO[]> {
    return this.customerService.CustomerGetCustomerByBonuscard(input).pipe(
      map((response: ResponseArgsOfIEnumerableOfCustomerInfoDTO) => {
        if (response.error) {
          console.error(response.message);
        }
        return response.result;
      })
    );
  }

  setShippingAddressAsDefault(shippingAddress: Address, customerId: number) {
    if (!shippingAddress) {
      return;
    }
    const params = {
      shippingAddressId: shippingAddress.id,
      customerId: customerId,
      isDefault: true,
    };
    if (shippingAddress.synced === true) {
      return this.customerService
        .CustomerModifyShippingAddressFlag(params)
        .pipe(
          map((response) => {
            if (response.error) {
              throw new Error(response.message);
            }
            return response.result;
          })
        );
    } else {
      return this.addShippingAddress(customerId, {
        ...shippingAddress,
        id: null,
      }).pipe(
        switchMap(() => {
          return this.customerService
            .CustomerModifyShippingAddressFlag(params)
            .pipe(
              map((response) => {
                if (response.error) {
                  throw new Error(response.message);
                }
                return response.result;
              })
            );
        })
      );
    }
  }

  setInvoiceAddressAsDefault(invoiceAddress: Address, customerId: number) {
    if (!invoiceAddress) {
      return;
    }
    if (invoiceAddress.synced === true) {
      const params = {
        payerId: invoiceAddress.id,
        customerId: customerId,
        isDefault: true,
      };
      return this.customerService.CustomerModifyPayerReference(params).pipe(
        map((response) => {
          if (response.error) {
            throw new Error(response.message);
          }
          return response.result;
        })
      );
    } else {
      return this.addInvoiceAddress(customerId, {
        ...invoiceAddress,
        id: null,
      }).pipe(
        switchMap((payerDto: PayerDTO) => {
          const params = {
            payerId: payerDto.id,
            customerId: customerId,
            isDefault: true,
          };
          return this.customerService.CustomerModifyPayerReference(params).pipe(
            map((response) => {
              if (response.error) {
                throw new Error(response.message);
              }
              return response.result;
            })
          );
        })
      );
    }
  }

  addShippingAddress(customerId: number, shippingAddress: Address) {
    if (!isNullOrUndefined(shippingAddress.id)) {
      const params = {
        shippingAddressId: shippingAddress.id,
        shippingAddress: this.mapper.fromAddressToShippingAddressDTO(
          shippingAddress,
          !isNullOrUndefined(shippingAddress.id)
        ),
        customerId: customerId,
      };
      return this.customerService.CustomerUpdateShippingAddress(params).pipe(
        catchError((error) => {
          if (error && error.error && error.error.error) {
            throw new Error(error.error.message);
          }
          if (
            error &&
            error.error &&
            error.error.invalidProperties &&
            Object.keys(error.error.invalidProperties) &&
            Object.keys(error.error.invalidProperties).length > 0
          ) {
            this.addAddressError$.next(error.error.invalidProperties);
          }
          return of(undefined);
        }),
        map((response) => {
          if (response) {
            this.addAddressError$.next(undefined);
            return response.result;
          }
        })
      );
    } else {
      const params = {
        shippingAddress: this.mapper.fromAddressToShippingAddressDTO(
          shippingAddress,
          !isNullOrUndefined(shippingAddress.id)
        ),
        customerId: customerId,
      };
      return this.customerService.CustomerCreateShippingAddress(params).pipe(
        catchError((error) => {
          if (error && error.error && error.error.error) {
            throw new Error(error.error.message);
          }
          if (
            error &&
            error.error &&
            error.error.invalidProperties &&
            Object.keys(error.error.invalidProperties) &&
            Object.keys(error.error.invalidProperties).length > 0
          ) {
            this.addAddressError$.next(error.error.invalidProperties);
          }
          return of(undefined);
        }),
        map((response) => {
          if (response) {
            this.addAddressError$.next(undefined);
            return response.result;
          }
        })
      );
    }
  }

  addInvoiceAddress(customerId: number, incoiceAddress: Address) {
    const payerDto = this.mapper.fromAddressToAssignedPAyerDto(incoiceAddress);
    if (!incoiceAddress) {
      return;
    }
    if (isNullOrUndefined(incoiceAddress.id)) {
      return this.payerService.PayerCreatePayer(payerDto).pipe(
        catchError((error) => {
          if (error && error.error && error.error.error) {
            throw new Error(error.error.message);
          }
          if (
            error &&
            error.error &&
            error.error.invalidProperties &&
            Object.keys(error.error.invalidProperties) &&
            Object.keys(error.error.invalidProperties).length > 0
          ) {
            this.addAddressError$.next(error.error.invalidProperties);
          }
          return of(undefined);
        }),
        switchMap((payerResponse: ResponseArgsOfPayerDTO) => {
          if (!payerResponse) {
            return of(undefined);
          }
          const referenceParamsparams = {
            customerId: customerId,
            payerId: payerResponse.result.id,
            isDefault: true,
          };
          return this.customerService
            .CustomerAddPayerReference(referenceParamsparams)
            .pipe(
              map((response) => {
                if (response.error) {
                  throw new Error(response.message);
                }

                if (!response.result || !response.result.payer) {
                  throw new Error(
                    response.message ||
                      'Beim Anlegen der Rechnungsadress-Referenz ist ein Fehler aufgetreten.'
                  );
                }
                this.addAddressError$.next(undefined);
                return response.result.payer.data;
              }),
              catchError((err) => {
                throw new Error(err.message);
              })
            );
        })
      );
    } else {
      const updatePayerParams = {
        payerId: incoiceAddress.id,
        payer: payerDto,
      };
      return this.payerService.PayerUpdatePayer(updatePayerParams).pipe(
        catchError((error) => {
          if (error && error.error && error.error.error) {
            throw new Error(error.error.message);
          }
          if (
            error &&
            error.error &&
            error.error.invalidProperties &&
            Object.keys(error.error.invalidProperties) &&
            Object.keys(error.error.invalidProperties).length > 0
          ) {
            this.addAddressError$.next(error.error.invalidProperties);
          }
          return of(undefined);
        }),
        map((response) => {
          this.addAddressError$.next(undefined);
          return response ? response.result : null;
        })
      );
    }
  }

  getCountries(): Observable<Country[]> {
    return this.customerService.CustomerGetCountries({}).pipe(
      map((response) => {
        if (response.error) {
          throw new Error(response.message);
        }
        return response.result.map(
          (t, index) =>
            <Country>{ index, id: t.id, key: t.isO3166_A_3, value: t.name }
        );
      })
    );
  }

  getBonusCards(customerId: number) {
    return this.customerService.CustomerGetBonuscards(customerId).pipe(
      catchError((error) => {
        console.error('Error getting customer cards', error);
        return of(undefined);
      }),
      map((response) => {
        if (response && response.error) {
          throw new Error(response.message);
        }
        return response && response.result ? response.result : [];
      })
    );
  }

  emailExists(email: string) {
    return this.customerService.CustomerEmailExists(email).pipe(
      map((response) => {
        if (response && response.error) {
          throw new Error(response.message);
        }
        return response && response.result;
      }),
      catchError((error) => {
        console.error(error);
        return of(undefined);
      })
    );
  }

  addressValid(address: AddressDTO): Observable<any> {
    return this.customerService.CustomerValidateAddress(address).pipe(
      catchError((error) => {
        console.error(error);
        return of(error);
      }),
      map((response: any) => {
        if (response && response.error) {
          return { invalidProperties: response.error.invalidProperties };
        }
        if (Array.isArray(response.result) && response.result.length < 1) {
          return { invalidProperties: { General: 'general error' } };
        }
        return response.result;
      })
    );
  }
}
