package component.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import component.model.TreeItemDto;
import component.rpc.TreeItemRpcService;
import database.dao.DefaultLoadDao;
import ervu_business_metrics.security.model.jwt.UserSession;
import ervu_business_metrics.security.service.ErvuJwtTokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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.GraphSource;
import ru.cg.webbpm.modules.database.bean.annotation.TypedColumn;
import ru.cg.webbpm.modules.database.bean.entity_graph.EntityColumn;
import ru.cg.webbpm.modules.database.bean.entity_graph.EntityColumnType;
import ru.cg.webbpm.modules.standard_annotations.editor.AdvancedProperty;
import ru.cg.webbpm.modules.standard_annotations.validation.NotNull;

/**
 * @author r.latypov
 */
@Service
public class TreeItemService {
  @Autowired
  private ErvuJwtTokenService ervuJwtTokenService;

  @NotNull
  public DefaultLoadDao loadDao;
  @GraphSource(value = TreeItemRpcService.class, scanMode = GraphSource.ScanMode.SELF)
  @NotNull
  public EntityColumn idColumn;
  @GraphSource(value = TreeItemRpcService.class, scanMode = GraphSource.ScanMode.SELF)
  @NotNull
  public EntityColumn parentIdColumn;
  @GraphSource(value = TreeItemRpcService.class, scanMode = GraphSource.ScanMode.SELF)
  @TypedColumn(colTypes = EntityColumnType.STRING)
  @NotNull
  public EntityColumn labelColumn;
  @GraphSource(value = TreeItemRpcService.class, scanMode = GraphSource.ScanMode.SELF)
  public EntityColumn businessIdColumn;
  @GraphSource(value = TreeItemRpcService.class, scanMode = GraphSource.ScanMode.SELF)
  public EntityColumn domainIdColumn;
  @AdvancedProperty
  @GraphSource(value = TreeItemRpcService.class, scanMode = GraphSource.ScanMode.SELF)
  public EntityColumn sortColumn;
  @AdvancedProperty
  @NotNull(predicate = "sortColumn != null")
  public SortOrder sortOrder;

  public List<TreeItemDto> loadTreeData() {
    List<TreeItemDto> loadedTreeItems = loadTreeItems();
    loadedTreeItems.forEach(item -> item.domainId = null);
    return loadedTreeItems.stream()
        .filter(item -> item.parentId == null)
        .toList();
  }

  public List<TreeItemDto> loadTreeDataByDomainId(String token, String[] roles) {
    UserSession session = ervuJwtTokenService.getUserSession(token);
    boolean hasMainRole = session.roles()
        .stream()
        .anyMatch(
            ervuRoleAuthority -> Arrays.asList(roles).contains(ervuRoleAuthority.getAuthority()));

    String domainId = session.recruitmentId();
    if (hasMainRole || domainId == null || domainIdColumn == null) {
      return loadTreeData();
    }
    List<TreeItemDto> filteredTreeItems = loadTreeItems().stream()
        .filter(item -> item.domainId.equalsIgnoreCase(domainId))
        .toList();
    filteredTreeItems.forEach(this::setDomainIdToNull);
    return filteredTreeItems;
  }

  private void setDomainIdToNull(TreeItemDto treeItem) {
    treeItem.domainId = null;
    TreeItemDto[] treeItemChildren = treeItem.children;
    if (treeItemChildren != null && treeItemChildren.length > 0) {
      for (TreeItemDto child : treeItemChildren) {
        setDomainIdToNull(child);
      }
    }
  }

  private List<TreeItemDto> loadTreeItems() {
    LoadOptions loadOptions = new LoadOptions();
    if (sortColumn != null && sortOrder != null) {
      loadOptions.setSortFields(Collections.singletonMap(sortColumn, sortOrder));
    }

    List<TreeItemDto> loadedList = this.loadDao.load(getColumns(), loadOptions).stream()
        .map(this::toTreeItemDto)
        .toList();

    Map<Object, List<TreeItemDto>> childrenMap = new HashMap<>();
    loadedList.forEach(item -> {
      Object parentId = item.parentId;
      if (parentId != null) {
        List<TreeItemDto> listFromMap = childrenMap.computeIfAbsent(parentId,
            k -> new ArrayList<>()
        );
        listFromMap.add(item);
      }
    });

    loadedList.forEach(item -> {
      List<TreeItemDto> children = childrenMap.get(item.id);
      if (children != null) {
        item.children = children.toArray(new TreeItemDto[0]);
      }
    });
    return loadedList;
  }

  private Set<EntityColumn> getColumns() {
    Set<EntityColumn> columnSet = new HashSet<>();
    columnSet.add(idColumn);
    columnSet.add(parentIdColumn);
    columnSet.add(labelColumn);
    if (businessIdColumn != null) {
      columnSet.add(businessIdColumn);
    }
    return columnSet;
  }

  private TreeItemDto toTreeItemDto(TableRow tableRow) {
    TreeItemDto treeItemDto = new TreeItemDto(
        tableRow.get(idColumn), tableRow.get(parentIdColumn), (String) tableRow.get(labelColumn)
    );
    if (treeItemDto.parentId == treeItemDto.id) {
      treeItemDto.parentId = null;
    }
    if (businessIdColumn != null) {
      treeItemDto.businessId = tableRow.get(businessIdColumn);
    }
    if (domainIdColumn != null) {
      treeItemDto.domainId = String.valueOf(tableRow.get(domainIdColumn));
    }
    return treeItemDto;
  }
}
