import { QueryTokenDTO } from '@generated/swagger/oms-api';
import { Subject } from 'rxjs';
import { IInputGroup, InputGroup } from './input-group';
import { InputType } from './input-type.enum';
import { IOrderBy, OrderBy } from './order-by';
import { FilterInput } from './input';

const ALLOWED_SUB_OPTIONS = [InputType.Bool, InputType.TriState];

export interface IFilter {
  filter?: Array<IInputGroup>;
  input?: Array<IInputGroup>;
  orderBy?: Array<IOrderBy>;
}

export class Filter implements IFilter {
  //#region implements IUiFilterQuerySettingsDTO
  private _filter?: Array<InputGroup>;
  get filter() {
    return this._filter;
  }

  private _input?: Array<InputGroup>;
  get input() {
    return this._input;
  }

  private _orderBy?: Array<OrderBy>;
  get orderBy() {
    return this._orderBy;
  }
  //#endregion

  readonly changes = new Subject<{ keys: (keyof IFilter)[]; orderBy: Filter }>();

  getQueryToken(): QueryTokenDTO {
    const token: QueryTokenDTO = {
      input: this.getInputAsStringDictionary(),
      filter: this.getFilterAsStringDictionary(),
      orderBy: this.orderBy?.filter((ob) => ob.selected).map((ob) => ob.toObject()),
    };

    return token;
  }

  getQueryParams(): Record<string, string> {
    const params: Record<string, string> = {};

    this.input?.forEach((inputGroup) => {
      const group = inputGroup.group;
      inputGroup.input.forEach((input) => {
        const key = input.key;
        params[`${group}_${key}`] = input.toStringValue();
      });
    });

    this.filter?.forEach((inputGroup) => {
      const group = inputGroup.group;
      inputGroup.input.forEach((input) => {
        let key = input.key;

        // Get queryParams with nested options only if subKey and nested options available
        const optionsWithChildOptions = this._getChildOptions(input);
        const hasSubKeys = !optionsWithChildOptions?.find((childOption) => childOption?.key === undefined);
        if (optionsWithChildOptions?.length > 0 && ALLOWED_SUB_OPTIONS.includes(input.type) && hasSubKeys) {
          for (let option of optionsWithChildOptions) {
            const subKey = option?.key;
            const values = option?.values
              ?.map((o) => o.toStringValue())
              ?.filter((i) => !!i)
              ?.join(';');

            params[`${group}_${key}_${subKey}`] = values;
          }
        } else {
          // If no nested options or correct subKeys available
          const values = inputGroup.input
            ?.filter((i) => i.key === key)
            ?.map((i) => i.toStringValue())
            ?.filter((i) => !!i)
            ?.join(';');

          params[`${group}_${key}`] = values;
        }
      });
    });

    this.orderBy
      ?.filter((ob) => ob.selected)
      .forEach((ob) => {
        params[`order_by_${ob.by}`] = ob?.desc ? 'desc' : 'asc';
      });

    return params;
  }

  private _getChildOptions(filterInput: FilterInput) {
    return filterInput?.options?.values?.filter((options) => !!options?.values);
  }

  fromQueryParams(params: Record<string, string>) {
    const groupKeys = Object.keys(params);

    groupKeys.forEach((gk) => {
      const group = gk.split('_')[0];
      const key = gk.substr(group.length + 1);

      this.input
        ?.filter((ig) => ig.group === group)
        .forEach((inputGroup) => {
          inputGroup.input
            .filter((i) => i.key === key)
            .forEach((input) => {
              input.fromStringValue(key, params[gk]);
            });
        });

      this.filter
        ?.filter((ig) => ig.group === group)
        .forEach((inputGroup) => {
          inputGroup.input.forEach((input, index) => {
            let value = params[gk];

            if (input.type === InputType.Text && value?.includes(';')) {
              const splitted = value?.split(';');
              if (splitted?.length >= index) {
                value = splitted[index];
              }
            }

            // Select nested options from queryParams
            const optionsWithChildOptions = this._getChildOptions(input);
            const parentKey = key?.split('_')[0];
            if (
              optionsWithChildOptions?.length > 0 &&
              parentKey === input.key &&
              ALLOWED_SUB_OPTIONS.includes(input.type)
            ) {
              for (let option of optionsWithChildOptions) {
                const subKey = key?.split('_')[1];
                if (subKey === option.key) {
                  let subValues = params[gk];
                  option?.values?.forEach((option) => {
                    option.trySetFromValue(subValues);
                  });
                }
              }
            } else {
              // If no nested options available
              input.fromStringValue(key, value);
            }
          });
        });
    });

    this.orderBy?.forEach((ob) => {
      const order = params[`order_by_${ob.by}`];
      const selected = (order && ob.desc && order === 'desc') || (!ob.desc && order === 'asc');
      ob.setSelected(selected);
    });
  }

  getInputAsStringDictionary(): Record<string, string> {
    const input: Record<string, string> = {};

    this.input?.forEach((ig) => {
      const group = ig.group;
      const inputWithValue = ig.input.find((i) => i.toStringValue());
      if (!inputWithValue) {
        return;
      }

      const inputsWithTheSameGroup = this.filter?.find((f) => f.group === group);
      const inputsWithTargetInput = inputsWithTheSameGroup?.input?.filter((ig) => ig.selected && ig.target === 'input');

      if (inputsWithTargetInput?.length) {
        inputsWithTargetInput.forEach((ifk) => {
          input[ifk.key] = inputWithValue.toStringValue();
        });
      } else {
        input[inputWithValue.key] = inputWithValue.value;
      }
    });

    return input;
  }

  getFilterAsStringDictionary(): Record<string, string> {
    const filter: Record<string, string> = {};
    for (const inputGroup of this.filter) {
      for (const input of inputGroup.input.filter((i) => i.target === 'filter')) {
        const subKeys = input.subKeys;
        let value = undefined;
        let key = undefined;

        if (subKeys.length === 0 || !ALLOWED_SUB_OPTIONS.includes(input.type)) {
          key = input.key;
          value = inputGroup.input
            ?.filter((i) => i.key === key)
            ?.map((i) => i.toStringValue())
            ?.filter((i) => !!i)
            ?.join(';');
          if (value) {
            filter[key] = value;
          }
        } else {
          for (let subKey of subKeys) {
            key = `${input.key}.${subKey}`;
            const options = input.options.values.filter((value) => value.key === subKey);
            value = options
              ?.map((i) => i.toStringValue())
              ?.filter((i) => !!i)
              ?.join(';');
            if (value) {
              filter[key] = value;
            }
          }
        }
      }
    }
    return filter;
  }

  // getKeyValue():

  toObject(): IFilter {
    return {
      filter: this.filter?.map((filter) => filter.toObject()),
      input: this.input?.map((input) => input.toObject()),
      orderBy: this.orderBy?.map((orderBy) => orderBy.toObject()),
    };
  }

  static create(settings: IFilter) {
    const target = new Filter();

    target._filter = settings?.filter?.map((filter) => InputGroup.create(filter, target)) || [];
    target._input = settings?.input?.map((input) => InputGroup.create(input, target)) || [];
    target._orderBy = settings?.orderBy?.map((orderBy) => OrderBy.create(orderBy, target)) || [];

    return target;
  }

  unselectAllFilterOptions() {
    this.filter?.forEach((uiInputGroup) =>
      uiInputGroup?.input?.forEach((uiInput) => {
        uiInput?.setSelected(undefined);
        uiInput?.setValue(undefined);
      }),
    );
  }

  static getQueryParamsFromQueryTokenDTO(queryToken: QueryTokenDTO): Record<string, string> {
    const queryParams: Record<string, string> = {};

    if (queryToken.input?.qs) {
      queryParams['main_qs'] = queryToken.input.qs;
    }

    if (queryToken.filter) {
      for (const key in queryToken.filter) {
        queryParams[`filter_${key}`] = queryToken.filter[key];
      }
    }

    return queryParams;
  }
}
