package ru.micord.ervu_dashboard.component.combobox;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import database.dao.ComboBoxLoadDao;
import model.ComboBoxModel;
import model.column.ComboBoxDisplayColumn;
import property.enums.DisplayType;
import property.grid.ColumnSort;
import property.grid.Formatter;
import service.field.ComboBoxService;
import utils.QueryUtils;

import ru.cg.webbpm.modules.database.api.bean.TableRow;
import ru.cg.webbpm.modules.database.api.dao.option.LoadOptions;
import ru.cg.webbpm.modules.database.api.dao.option.SortOrder;
import ru.cg.webbpm.modules.database.bean.annotation.LocalGraphSource;
import ru.cg.webbpm.modules.database.bean.entity_graph.EntityColumn;
import ru.cg.webbpm.modules.database.bean.filter.EntityFilter;
import ru.cg.webbpm.modules.database.bean.filter.FilterOperation;
import ru.cg.webbpm.modules.standard_annotations.editor.AdvancedProperty;
import ru.cg.webbpm.modules.standard_annotations.editor.Visible;
import ru.cg.webbpm.modules.standard_annotations.validation.NotNull;

/**
 * @author Golovlyev
 */
public class ErvuComboBoxServiceImpl implements ComboBoxService {

  @LocalGraphSource(sourceFieldName = "loadDao")
  @Visible(predicate = "false")
  @Deprecated
  public EntityColumn columnForSort;

  @Visible(predicate = "false")
  @Deprecated
  public SortOrder sortOrder;

  @NotNull
  public DisplayType displayType;

  @LocalGraphSource(sourceFieldName = "loadDao")
  @NotNull(predicate = "displayType == DisplayType.ONE_COLUMN")
  @Visible(predicate = "displayType == DisplayType.ONE_COLUMN")
  public EntityColumn displayColumn;

  @NotNull(predicate = "displayType == DisplayType.MULTI_COLUMN")
  @Visible(predicate = "displayType == DisplayType.MULTI_COLUMN")
  public ComboBoxDisplayColumn[] displayColumns;

  @AdvancedProperty
  public ComboBoxDisplayColumn[] dropDownColumns;
  @AdvancedProperty
  public FilterService[] filterServices;

  @Visible(predicate = "displayType == DisplayType.MULTI_COLUMN")
  public String glue;

  @LocalGraphSource(sourceFieldName = "loadDao")
  public EntityColumn businessIdColumn;

  @LocalGraphSource(sourceFieldName = "loadDao")
  public EntityColumn parentControlReference;

  @LocalGraphSource(sourceFieldName = "loadDao")
  public EntityColumn filterColumn;

  public ComboBoxLoadDao loadDao;

  public ColumnSort[] columnSorts = new ColumnSort[0];

  @Override
  public List<ComboBoxModel> loadData() {
    return load(createLoadOptions(null));
  }

  @Override
  public List<ComboBoxModel> loadDataWithFilter(Object filterValue) {
    EntityFilter entityFilter = getFilterColumnEntityFilter(filterValue);
    return load(createLoadOptions(Collections.singletonList(entityFilter)));
  }

  @Override
  public List<ComboBoxModel> loadDataByParentValueWithFilter(Object parentValue,
      Object filterValue) {
    List<EntityFilter> entityFilters = new ArrayList<>();
    entityFilters.add(getParentColumnEntityFilter(parentValue));
    if (filterValue != null) {
      entityFilters.add(getFilterColumnEntityFilter(filterValue));
    }
    return load(createLoadOptions(entityFilters));
  }

  protected ComboBoxModel convertToComboBoxModel(final TableRow row) {
    String dropDownLabel = null;
    String displayLabel;
    if (displayType == DisplayType.MULTI_COLUMN) {
        displayLabel = createLabelByColumnAndRow(row, displayColumns);
    }
    else if (displayType == DisplayType.ONE_COLUMN) {
      displayLabel = String.valueOf(row.get(displayColumn));
    }
    else {
      throw new RuntimeException("Incorrect combobox column type " + displayType);
    }
    if (dropDownColumns != null && dropDownColumns.length > 0) {
      dropDownLabel = createLabelByColumnAndRow(row, dropDownColumns);
    }
    return new ComboBoxModel(row.get(QueryUtils.getIdField(loadDao)),
        row.get(businessIdColumn), displayLabel, (Boolean) row.get(loadDao.getValidEntityColumn()), dropDownLabel
    );
  }

  protected String createLabelByColumnAndRow(TableRow row, ComboBoxDisplayColumn[] columns) {
    return Arrays.stream(columns)
        .map(column -> formatValue(column, row))
        .filter(value -> !value.equals(""))
        .collect(Collectors.joining(glue != null ? glue : ""));
  }

  private LoadOptions createLoadOptions(Collection<EntityFilter> entityFilters) {
    LoadOptions loadOptions = new LoadOptions();
    List<EntityFilter> resultEntityFilters =
        entityFilters == null ? new ArrayList<>() : new ArrayList<>(entityFilters);

    if (filterServices != null && filterServices.length != 0) {
      resultEntityFilters.addAll(Arrays.stream(filterServices)
          .map(FilterService::getEntityFilter)
          .filter(Objects::nonNull)
          .toList());
    }

    if (!resultEntityFilters.isEmpty()) {
      loadOptions.setEntityFilters(resultEntityFilters);
    }
    return loadOptions;
  }

  private String formatValue(ComboBoxDisplayColumn column, TableRow row) {
    Object value = row.get(column.getEntityColumn());
    Formatter<?, Object> formatter = column.getFormatter();
    Object formattedValue = formatter != null ? formatter.format(value) : value;
    return formattedValue == null || Objects.equals(formattedValue, "")
           ? column.getEmptyValue()
           : column.getPrefix() + formattedValue + column.getPostfix();
  }

  private List<ComboBoxModel> load(LoadOptions loadOptions) {

    if (columnSorts.length > 0) {

      for (ColumnSort columnSort : columnSorts) {
        loadOptions.addSortField(columnSort.field, columnSort.sortOrder);
      }
    }
    else if (displayType == DisplayType.MULTI_COLUMN) {
      Arrays.stream(displayColumns).forEach(searchDisplayColumn -> {
        loadOptions.addSortField(searchDisplayColumn.getEntityColumn(), SortOrder.ASC);
      });
    }
    else if (displayType == DisplayType.ONE_COLUMN) {
      loadOptions.addSortField(displayColumn, SortOrder.ASC);
    }
    List<TableRow> loadedData = loadDao.load(getColumnsToLoad(), loadOptions);
    return loadedData.stream()
        .map(this::convertToComboBoxModel)
        .collect(Collectors.toList());
  }

  public ComboBoxModel loadModelWithoutGraphConditions(Object id, Object parentValue) {
    LoadOptions loadOptions = new LoadOptions();
    List<EntityFilter> filters = getFilters(parentValue);
    loadOptions.setEntityFilters(filters);

    TableRow row = loadDao.loadWithoutGraphConditionsByPk(getColumnsToLoad(), loadOptions, id);

    return row.getColumnToDataMap().size() > 1 ? convertToComboBoxModel(row) : null;
  }


  protected Set<EntityColumn> getColumnsToLoad() {
    Set<EntityColumn> columnsToLoad = new HashSet<>();
    columnsToLoad.add(QueryUtils.getIdField(loadDao));
    if (displayType == DisplayType.MULTI_COLUMN) {
      List<EntityColumn> entityColumns = Arrays.stream(displayColumns)
          .map(ComboBoxDisplayColumn::getEntityColumn)
          .collect(Collectors.toList());
      columnsToLoad.addAll(entityColumns);
    }
    else if (displayType == DisplayType.ONE_COLUMN) {
      columnsToLoad.add(displayColumn);
    }
    else {
      throw new RuntimeException("Incorrect combobox column type " + displayType);
    }
    if (businessIdColumn != null) {
      columnsToLoad.add(businessIdColumn);
    }
    return columnsToLoad;
  }

  private EntityFilter getFilterColumnEntityFilter(final Object filterValue) {
    return new EntityFilter(filterValue, FilterOperation.EQUAL_IGNORE_CASE, filterColumn);
  }

  private EntityFilter getParentColumnEntityFilter(final Object parentValue) {
    if (parentValue instanceof Collection) {
      return new EntityFilter(parentValue, FilterOperation.IN, parentControlReference);
    }
    return new EntityFilter(parentValue, FilterOperation.EQUAL, parentControlReference);
  }

  private List<EntityFilter> getFilters(Object parentValue) {
    List<EntityFilter> filters = new ArrayList<>();
    if (parentValue != null) {
      filters.add(getParentColumnEntityFilter(parentValue));
    }
    return filters;
  }

}
