package ervu_dashboard.component.chart;

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

import component.chart.dto.ChartDataSetDto;
import component.chart.dto.ChartPointDto;
import component.chart.model.AggregationDataSet;
import component.chart.model.ChartColumnSort;
import component.chart.model.StaticDataSet;
import component.chart.service.MultiChartDataSetService;
import component.chart.service.impl.AbstractChartDatasetService;
import ervu_dashboard.component.filter.FilterReferences;
import ervu_dashboard.model.chart.ChartDatasetType;
import ervu_dashboard.model.chart.ColumnAggregationDataSet;
import ervu_dashboard.model.chart.ErvuChartDataSetDto;
import model.Filter;
import model.FilterModel;

import ru.cg.webbpm.modules.database.api.bean.TableRow;
import ru.cg.webbpm.modules.database.api.dao.LoadDao;
import ru.cg.webbpm.modules.database.api.dao.option.LoadOptions;
import ru.cg.webbpm.modules.database.bean.entity_graph.AggregateFuncField;
import ru.cg.webbpm.modules.database.bean.entity_graph.EntityColumn;
import ru.cg.webbpm.modules.database.bean.filter.EntityFilter;
import ru.cg.webbpm.modules.standard_annotations.editor.Visible;
import ru.cg.webbpm.modules.standard_annotations.validation.NotNull;

import static java.util.Arrays.stream;

/**
 * @author Vitaly Chekushkin
 */
public class ErvuMultiChartDataSetService extends AbstractChartDatasetService implements
    MultiChartDataSetService {

  @NotNull
  public LoadDao loadDao;

  @NotNull
  public ChartDatasetType datasetType;

  @NotNull(predicate = "datasetType==ChartDatasetType.AGGREGATION")
  @Visible(predicate = "datasetType==ChartDatasetType.AGGREGATION")
  public AggregationDataSet aggregationDataSet;

  @NotNull(predicate = "datasetType==ChartDatasetType.STATIC")
  @Visible(predicate = "datasetType==ChartDatasetType.STATIC")
  public StaticDataSet staticDataSet;

  @NotNull(predicate = "datasetType==ChartDatasetType.COLUMN_AGGREGATION")
  @Visible(predicate = "datasetType==ChartDatasetType.COLUMN_AGGREGATION")
  public ColumnAggregationDataSet columnAggregationDataSet;

  private FilterReferences filterReferences;

  @Override
  protected void start() {
    super.start();
    this.filterReferences = getScript(FilterReferences.class);
  }

  @Override
  public List<ChartDataSetDto> loadDataSets(Integer offset, Integer limit, Filter[] filters) {
    LoadOptions loadOptions = loadOptions(offset, limit, filters);
    return switch (datasetType) {
      case AGGREGATION -> loadAggregationDataSets(loadOptions);
      case STATIC -> loadStaticDataSets(loadOptions);
      case COLUMN_AGGREGATION -> loadColumnAggregationDataSets(loadOptions);
    };
  }

  protected List<ChartDataSetDto> loadAggregationDataSets(LoadOptions loadOptions) {

    for (ChartColumnSort columnSort : aggregationDataSet.columnSorts) {
      loadOptions.addSortField(columnSort.field, columnSort.sortOrder);
    }
    EntityColumn[] groupByColumnsArray = aggregationDataSet.groupByColumns;
    Set<EntityColumn> groupByColumns = stream(groupByColumnsArray).collect(Collectors.toSet());
    EntityColumn labelColumn = aggregationDataSet.labelColumn != null
                               ? aggregationDataSet.labelColumn
                               : groupByColumnsArray[groupByColumnsArray.length - 1];

    if (!groupByColumns.contains(labelColumn)) {
      throw new IllegalStateException(
          "Label column " + labelColumn + " must be in groupColumns array");
    }
    Set<AggregateFuncField> aggregateFuncFields = stream(aggregationDataSet.aggregationData).map(
            aggregationData -> stream(aggregationData.aggregationFunctionData).map(
                aggregationFunctionData -> new AggregateFuncField(aggregationData.aggregationColumn,
                    aggregationFunctionData.aggregationFunction
                )).collect(Collectors.toList()))
        .flatMap(Collection::stream)
        .collect(Collectors.toSet());

    List<TableRow> rows = loadDao.loadAggregations(groupByColumns, aggregateFuncFields,
        loadOptions
    );

    return stream(aggregationDataSet.aggregationData).map(dataItem ->
        stream(dataItem.aggregationFunctionData).map(aggFuncData -> {

          List<ChartPointDto> points = rows.stream().map(tableRow -> {
            Object xAxesValue = getAxesValue(tableRow, labelColumn,
                aggregationDataSet.labelColumFormatter
            );
            Object yAxesValue = getAxesValue(tableRow, dataItem.aggregationColumn,
                dataItem.aggregationColumnFormatter
            );
            return new ChartPointDto(xAxesValue, yAxesValue);
          }).collect(Collectors.toList());

          ChartDataSetDto chartDataSetDto = new ChartDataSetDto(aggFuncData.dataLabel,
              aggFuncData.chartType.toString(), points
          );
          chartDataSetDto.setBorderColor(aggFuncData.borderColor);
          chartDataSetDto.setBackgroundColor(aggFuncData.backgroundColor);
          chartDataSetDto.setxAxisID(aggFuncData.xAxesId);
          chartDataSetDto.setyAxisID(aggFuncData.yAxesId);
          chartDataSetDto.setTension(aggFuncData.tension);
          chartDataSetDto.setOrder(aggFuncData.drawOrder);
          return chartDataSetDto;
        }).collect(Collectors.toList())).flatMap(Collection::stream).collect(Collectors.toList());
  }

  protected List<ChartDataSetDto> loadStaticDataSets(LoadOptions loadOptions) {

    for (ChartColumnSort columnSort : staticDataSet.columnSorts) {
      loadOptions.addSortField(columnSort.field, columnSort.sortOrder);
    }
    Set<EntityColumn> columns = new HashSet<>();
    stream(staticDataSet.staticData).forEach(
        staticChartData -> columns.addAll(staticChartData.columns()));

    List<TableRow> rows = loadDao.load(columns, loadOptions);

    return stream(staticDataSet.staticData).map(staticChartData -> {
      List<ChartPointDto> points = rows.stream().map(tableRow -> {
        Object xAxesValue = getAxesValue(tableRow, staticChartData.labelColumn,
            staticChartData.labelColumnFormatter
        );
        Object yAxesValue = getAxesValue(tableRow, staticChartData.dataColumn,
            staticChartData.dataColumnFormatter
        );
        return new ChartPointDto(xAxesValue, yAxesValue);
      }).collect(Collectors.toList());

      ChartDataSetDto chartDataSetDto = new ChartDataSetDto(staticChartData.dataLabel,
          staticChartData.chartType.toString(), points
      );
      chartDataSetDto.setData(points);
      chartDataSetDto.setLabel(staticChartData.dataLabel);
      chartDataSetDto.setBorderColor(staticChartData.borderColor);
      chartDataSetDto.setBackgroundColor(staticChartData.backgroundColor);
      chartDataSetDto.setxAxisID(staticChartData.xAxesId);
      chartDataSetDto.setyAxisID(staticChartData.yAxesId);
      chartDataSetDto.setTension(staticChartData.tension);
      chartDataSetDto.setOrder(staticChartData.drawOrder);
      return chartDataSetDto;
    }).collect(Collectors.toList());
  }

  protected List<ChartDataSetDto> loadColumnAggregationDataSets(LoadOptions loadOptions) {
    ColumnAggregationDataSet dataSet = columnAggregationDataSet;

    Set<AggregateFuncField> aggregateFuncFields = stream(dataSet.aggregationData)
        .map(aggData -> new AggregateFuncField(aggData.aggregationColumn,
            aggData.aggregationFunction
        ))
        .collect(LinkedHashSet::new, Set::add, Set::addAll);

    TableRow tableRow = Optional.ofNullable(loadDao.loadAggregations(
        aggregateFuncFields, loadOptions
    )).orElse(new TableRow());

    List<String> backgroundColors = new ArrayList<>();

    List<ChartPointDto> points = stream(dataSet.aggregationData)
        .map(aggData -> {
          backgroundColors.add(aggData.borderColor);
          return new ChartPointDto(
              aggData.label,
              getAxesValue(tableRow, aggData.aggregationColumn, aggData.aggregationColumnFormatter)
          );
        }).toList();

    ErvuChartDataSetDto chartDataSetDto = new ErvuChartDataSetDto(
        dataSet.dataSetLabel,
        dataSet.chartType.toString(),
        points
    );

    chartDataSetDto.setBackgroundColors(backgroundColors.toArray(new String[0]));
    chartDataSetDto.setBorderColor(dataSet.borderColor);
    chartDataSetDto.setxAxisID(dataSet.xAxesId);
    chartDataSetDto.setyAxisID(dataSet.yAxesId);
    chartDataSetDto.setTension(dataSet.tension);
    chartDataSetDto.setOrder(dataSet.drawOrder);
    chartDataSetDto.setBorderRadius(dataSet.borderRadius);
    chartDataSetDto.setBarPercentage(dataSet.barPercentage);

    return Collections.singletonList(chartDataSetDto);
  }

  protected LoadOptions loadOptions(Integer offset, Integer limit, Filter[] filters) {
    LoadOptions loadOptions = new LoadOptions();
    loadOptions.setOffset(offset);
    loadOptions.setLimit(limit);
    loadOptions.setEntityFilterGroup(getEntityFilterGroup(filters));
    return loadOptions;
  }

  @Override
  protected List<EntityFilter> convertFilterModels(Filter filter) {
    return Arrays.stream(filter.filterModels)
        .map(filterModel -> getComponentFilter(filter.componentGuid, filterModel))
        .collect(Collectors.toList());
  }

  @Override
  protected EntityFilter getComponentFilter(String componentGuid, FilterModel filterModel) {
    if (filterReferences != null) {
      return filterReferences.toEntityFilter(componentGuid, filterModel);
    }
    else {
      return super.getComponentFilter(componentGuid, filterModel);
    }
  }
}
