import { Injectable } from '@angular/core';
import { FormBuilder, FormGroup, Validators, FormArray, AbstractControl } from '@angular/forms';
import { Observable } from 'rxjs';
import {
  OrderItemListItemDTO,
  VATDTO,
  OrderItemDTO,
  OrderItemProcessingStatusValue,
  StatusValues,
  EnvironmentChannel,
  VATType,
  OrderItemSubsetDTO,
} from '@swagger/oms';
import { DetailsFacade } from '@shelf-store/details';
import { take, filter } from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { EnvironmentChannelPipe } from '../pipes/environment-channel.pipe';
import { DatePipe } from '@angular/common';
import { ProcessingStatusNameMap } from '../constants';
import { Select } from '@ngxs/store';
import { VatState } from '../../../core/store/state/vat.state';
import { toDecimalPlaces } from '../../../core/utils/price.util';
import { ActionResult } from '../../../core/models/action-result.model';

@Injectable({ providedIn: 'root' })
export class ShelfEditFormService {
  @Select(VatState.getVats) vats$: Observable<VATDTO[]>;
  forms = new Map<string, FormGroup>();

  public get processingStatus(): number[] {
    return Array.from(ProcessingStatusNameMap.keys());
  }

  public get availableProcessingStatus(): number[] {
    return this.processingStatus.filter((status) => {
      const result = ProcessingStatusNameMap.get(status);
      if (!result || !result.disabled) {
        return false;
      }
      return true;
    });
  }

  public get vatOptions$(): Observable<VATDTO[]> {
    return this.vats$.pipe(filter((vats) => !!vats.length));
  }

  constructor(private formBuilder: FormBuilder, private detailsStoreFacade: DetailsFacade) {}

  public getItemsForm(compartmentCode: string): FormArray {
    if (!this.forms.has(compartmentCode)) {
      return new FormArray([]);
    }
    return this.forms.get(compartmentCode).get('items') as FormArray;
  }

  public getCustomerName(compartmentCode: string): { firstName: string; lastName: string } {
    if (!this.forms.has(compartmentCode)) {
      return {
        firstName: 'unbekannt',
        lastName: '',
      };
    }

    const form = this.forms.get(compartmentCode);
    return {
      firstName: form.get('firstName').value || '',
      lastName: form.get('lastName').value || '',
    };
  }

  async createFormByOrderNumber(orderNumber: string, processingStatus: OrderItemProcessingStatusValue): Promise<FormGroup> {
    const controlsConfig = await this.getControlsConfigForOrder(orderNumber, processingStatus);

    const form = this.formBuilder.group(controlsConfig);
    this.forms.set(orderNumber, form);

    return this.forms.get(orderNumber);
  }

  async createFormByCompartmentCode(compartmentCode: string, processingStatus: OrderItemProcessingStatusValue): Promise<FormGroup> {
    const controlsConfig = await this.getControlsConfigForCompartment(compartmentCode, processingStatus);

    const form = this.formBuilder.group(controlsConfig);
    this.forms.set(compartmentCode, form);

    return this.forms.get(compartmentCode);
  }

  async getControlsConfigForOrder(orderNumber: string, processingStatus: OrderItemProcessingStatusValue): Promise<{ [key: string]: any }> {
    const orderItems$: Observable<OrderItemListItemDTO[]> = this.detailsStoreFacade.getOrderItemsByOrderNumberAndProcessingStatus$(
      orderNumber,
      processingStatus
    );

    return this.createControlConfig(orderItems$);
  }

  async getControlsConfigForCompartment(
    compartmentCode: string,
    processingStatus: OrderItemProcessingStatusValue
  ): Promise<{ [key: string]: any }> {
    const orderItems$: Observable<OrderItemListItemDTO[]> = this.detailsStoreFacade.getOrderItemsByCompartmentCodeAndProcessingStatus$(
      compartmentCode,
      processingStatus
    );
    return this.createControlConfig(orderItems$);
  }

  private async createControlConfig(orderItems$: Observable<OrderItemListItemDTO[]>): Promise<{ [key: string]: any }> {
    const orderItems = await orderItems$
      .pipe(
        filter((items) => !isNullOrUndefined(items) && !!items.length),
        take(1)
      )
      .toPromise();

    const vats = await this.vats$.pipe(take(1)).toPromise();

    return {
      ...this.getGeneralForm(orderItems),
      items: this.formBuilder.array(orderItems.map((orderItem) => this.getItemForm(orderItem, vats))),
    };
  }

  getGeneralForm(orderItems: OrderItemListItemDTO[]): { [key: string]: any } {
    const baseOrderItem = orderItems[0];
    return {
      firstName: baseOrderItem.firstName,
      lastName: baseOrderItem.lastName,
      compartmentCode: [{ value: baseOrderItem.compartmentCode, disabled: false }],
      orderId: [{ value: baseOrderItem.orderId, disabled: true }],
      orderNumber: [
        {
          value: baseOrderItem.orderNumber,
          disabled: true,
        },
      ],
      orderDate: [
        {
          value: new DatePipe('de').transform(baseOrderItem.orderDate),
          disabled: true,
        },
      ],
      clientChannel: [
        {
          value: new EnvironmentChannelPipe().transform(baseOrderItem.clientChannel),
          disabled: true,
        },
      ],
      buyerNumber: [{ value: baseOrderItem.buyerNumber, disabled: true }],
      processingStatus: [{ value: baseOrderItem.processingStatus, disabled: false }],
      estimatedShippingDate: [{ value: baseOrderItem.estimatedShippingDate, disabled: false }],
      pickUpDeadline: [
        {
          value: baseOrderItem.pickUpDeadline,
          disabled: false,
        },
      ],
    };
  }

  getDataFromGeneralForm(form: FormGroup) {
    return {
      firstName: form.get('firstName').value,
      lastName: form.get('lastName').value,
      compartmentCode: form.get('compartmentCode').value,
      orderId: Number(form.get('orderId').value),
      orderNumber: form.get('orderNumber').value,
      orderDate: new Date(form.get('orderDate').value),
      clientChannel: Number(form.get('clientChannel').value) as EnvironmentChannel,
      buyerNumber: form.get('buyerNumber').value,
      processingStatus: Number(form.get('processingStatus').value) as OrderItemProcessingStatusValue,
      pickUpDeadline: String(form.get('pickUpDeadline').value || ''),
      estimatedShippingDate: String(form.get('estimatedShippingDate').value || ''),
    };
  }

  getItemForm(orderItem: OrderItemListItemDTO, vats: VATDTO[]): FormGroup {
    return this.formBuilder.group({
      orderId: [{ value: orderItem.orderId, disabled: true }],
      orderItemId: [{ value: orderItem.orderItemId, disabled: true }],
      orderItemSubsetId: [{ value: orderItem.orderItemSubsetId, disabled: true }],
      quantity: [{ value: orderItem.quantity, disabled: true }],
      price: [
        {
          value: toDecimalPlaces(orderItem.price, 2).replace('.', ','),
          disabled: false,
        },
        [Validators.required, Validators.pattern(/^\d+(,\d{1,2})?$/)],
      ],
      ean: [
        {
          value: orderItem.product.ean,
          disabled: false,
        },
      ],
      supplier: [{ value: orderItem.supplier, disabled: true }],
      title: [{ value: orderItem.product.name, disabled: true }],
      ssc: [
        {
          value: orderItem.ssc ? orderItem.ssc + ' - ' + orderItem.sscText : '-',
          disabled: false,
        },
      ],
      clientChannel: [{ value: orderItem.clientChannel, disabled: true }],
      targetBranch: [{ value: orderItem.targetBranch, disabled: true }],
      vat: [
        {
          value: orderItem.vatType,
          disabled: false,
        },
      ],
      comment: [{ value: orderItem.specialComment, disabled: false }],
    });
  }

  getOrderItemFromItemForm(form: AbstractControl): OrderItemDTO {
    return {
      id: Number(form.get('orderItemId').value),
      quantity: Number(form.get('quantity').value),
      grossPrice: {
        vat: { vatType: Number(form.get('vat').value) as VATType },
        value: { value: Number(form.get('price').value.replace(',', '.')) },
      },
      product: { ean: String(form.get('ean').value) },
    };
  }

  getOrderItemSubsetFromItemForm(ctrl: AbstractControl, form: FormGroup): OrderItemSubsetDTO & { orderItemId: number } {
    const sscFull: string = ctrl.get('ssc').value;
    let ssc = sscFull.split('-')[0];
    let sscText = sscFull.substr(ssc.length + 1);

    ssc = ssc.trim();
    sscText = sscText.trim();

    return {
      id: Number(ctrl.get('orderItemSubsetId').value),
      orderItemId: Number(ctrl.get('orderItemId').value),
      orderItemSubsetNumber: ctrl.get('orderItemSubsetId').value,
      processingStatus: form.get('processingStatus').value,
      estimatedShippingDate: String(form.get('estimatedShippingDate').value),
      compartmentCode: !!form.get('compartmentCode').value ? String(form.get('compartmentCode').value) : null,
      specialComment: String(ctrl.get('comment').value || ''),
      ssc,
      sscText,
    };
  }

  async submit(form: FormGroup, processingStatus: OrderItemProcessingStatusValue): Promise<ActionResult<boolean>> {
    const formData = this.getDataFromGeneralForm(form);

    const orderItemsToPatch = this.prepareOrderItems(form);
    const orderItemSubsetsToPatch = this.prepareOrderItemSubsets(form);

    const newProcessingStatus = Number(form.get('processingStatus').value) as OrderItemProcessingStatusValue;

    try {
      await this.detailsStoreFacade.patchOrderItems(orderItemsToPatch);
      await this.detailsStoreFacade.patchOrderItemSubsets(orderItemSubsetsToPatch);

      if (newProcessingStatus !== processingStatus) {
        const changeStatusData = this.prepareStatusChange(form, newProcessingStatus);
        if (newProcessingStatus === 128) {
          await this.detailsStoreFacade.arrived(changeStatusData, undefined);
        } else {
          await this.detailsStoreFacade.changeStatus(changeStatusData);
        }
      }
    } catch (error) {
      console.log('%cSubmit Error: ', 'color: red; font-weight: bold', error);
      return {
        error: true,
        errorReasons: error.invalidProperties || {},
        http: {
          code: error.status || 500,
        },
        message: 'Speichern fehlgeschlagen',
        result: false,
      };
    }

    return {
      error: false,
      result: true,
    };
  }

  private prepareOrderItems(form: FormGroup) {
    const orderItems = (form.get('items') as FormArray).controls.map((control) => this.getOrderItemFromItemForm(control));

    return orderItems.map((orderItem) => ({
      orderId: Number(form.get('orderId').value),
      orderItemId: orderItem.id,
      orderItem,
    }));
  }

  private prepareOrderItemSubsets(
    form: FormGroup
  ): {
    orderId: number;
    orderItemId: number;
    orderItemSubsetId: number;
    orderItemSubset: Partial<OrderItemSubsetDTO>;
  }[] {
    const subsetItems = (form.get('items') as FormArray).controls.map((control) => this.getOrderItemSubsetFromItemForm(control, form));
    const { pickUpDeadline } = this.getDataFromGeneralForm(form);

    return subsetItems.map((item) => ({
      orderId: form.get('orderId').value,
      orderItemId: item.orderItemId,
      orderItemSubsetId: item.id,
      orderItemSubset: { ...item, compartmentStop: pickUpDeadline },
    }));
  }

  private prepareStatusChange(
    form: FormGroup,
    processingStatus: OrderItemProcessingStatusValue
  ): {
    orderItemSubsetId: number;
    orderItemId: number;
    orderId: number;
    data: StatusValues;
  }[] {
    const items = (form.get('items') as FormArray).controls.map((itemForm) => this.getOrderItemSubsetFromItemForm(itemForm, form));

    return items.map((item) => ({
      orderId: Number(form.get('orderId').value),
      orderItemId: item.orderItemId,
      orderItemSubsetId: Number(item.orderItemSubsetNumber),
      data: { processingStatus } as StatusValues,
    }));
  }
}
