package org.dotwebstack.framework.backend.postgres;

import static java.util.function.Predicate.not;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.dotwebstack.framework.backend.postgres.model.PostgresObjectField;
import org.dotwebstack.framework.backend.postgres.model.PostgresObjectType;
import org.dotwebstack.framework.core.backend.BackendLoaderFactory;
import org.dotwebstack.framework.core.backend.BackendModule;
import org.dotwebstack.framework.core.datafetchers.aggregate.AggregateHelper;
import org.dotwebstack.framework.core.model.ObjectField;
import org.dotwebstack.framework.core.model.ObjectType;
import org.springframework.stereotype.Component;

@AllArgsConstructor
@Component
class PostgresBackendModule implements BackendModule<PostgresObjectType> {

  private final PostgresBackendLoaderFactory backendLoaderFactory;

  @Override
  public Class<PostgresObjectType> getObjectTypeClass() {
    return PostgresObjectType.class;
  }

  @Override
  public BackendLoaderFactory getBackendLoaderFactory() {
    return backendLoaderFactory;
  }

  @Override
  public void init(Map<String, ObjectType<? extends ObjectField>> objectTypes) {
    List<PostgresObjectField> allFields = getAllFields(objectTypes);

    setTargetType(objectTypes, allFields);
    setMappedByObjectField(objectTypes, allFields);
    setAggregationOfType(objectTypes, allFields);
  }

  private List<PostgresObjectField> getAllFields(Map<String, ObjectType<? extends ObjectField>> objectTypes) {
    var postgresObjectTypes = objectTypes.values()
        .stream()
        .map(PostgresObjectType.class::cast)
        .filter(not(PostgresObjectType::isNested))
        .collect(Collectors.toList());

    return postgresObjectTypes.stream()
        .flatMap(objectType -> objectType.getFields()
            .values()
            .stream())
        .collect(Collectors.toList());
  }

  private void setTargetType(Map<String, ObjectType<? extends ObjectField>> objectTypes,
      List<PostgresObjectField> allFields) {
    allFields.stream()
        .filter(objectField -> objectTypes.containsKey(objectField.getType()))
        .forEach(objectField -> {
          var targetType = (PostgresObjectType) objectTypes.get(objectField.getType());
          objectField.setTargetType(targetType);
        });
  }

  private void setMappedByObjectField(Map<String, ObjectType<? extends ObjectField>> objectTypes,
      List<PostgresObjectField> allFields) {
    allFields.stream()
        .filter(objectField -> isNotEmpty(objectField.getMappedBy()))
        .forEach(objectField -> {
          var type = StringUtils.isNotEmpty(objectField.getAggregationOf()) ? objectField.getAggregationOf()
              : objectField.getType();

          var objectType = objectTypes.get(type);

          PostgresObjectField mappedByObjectField = (PostgresObjectField) objectType.getFields()
              .get(objectField.getMappedBy());

          objectField.setMappedByObjectField(mappedByObjectField);
        });
  }

  private void setAggregationOfType(Map<String, ObjectType<? extends ObjectField>> objectTypes,
      List<PostgresObjectField> allFields) {
    allFields.stream()
        .filter(AggregateHelper::isAggregate)
        .forEach(objectField -> {
          var aggregationOfType = (PostgresObjectType) objectTypes.get(objectField.getAggregationOf());

          objectField.setAggregationOfType(aggregationOfType);
        });
  }
}

@@ -156,16 +156,116 @@
 otEmpty;
+%0Aimport static org.dotwebstack.framework.core.helpers.ExceptionHelper.invalidConfigurationException;
 %0A%0Aimport
@@ -303,16 +303,43 @@
 il.Map;%0A
+import java.util.Optional;%0A
 import j
@@ -2468,33 +2468,27 @@
 argetType = 
-(Postgres
+get
 ObjectType) 
@@ -2477,34 +2477,33 @@
  = getObjectType
-) 
+(
 objectTypes.get(
@@ -2489,37 +2489,34 @@
 Type(objectTypes
-.get(
+, 
 objectField.getT
@@ -3026,119 +3026,44 @@
 var 
-objectType = objectTypes.get(type);%0A%0A          PostgresObjectField mappedByObjectField = (PostgresObjectField) 
+mappedByObjectField = getObjectType(
 obje
@@ -3060,32 +3060,40 @@
 tType(objectType
+s, type)
 .getFields()%0A   
@@ -3089,30 +3089,8 @@
 ield
-s()%0A              .get
 (obj
@@ -3477,25 +3477,19 @@
 fType = 
-(Postgres
+get
 ObjectTy
@@ -3490,18 +3490,17 @@
 jectType
-) 
+(
 objectTy
@@ -3506,13 +3506,10 @@
 ypes
-.get(
+, 
 obje
@@ -3533,25 +3533,24 @@
 ationOf());%0A
-%0A
           ob
@@ -3615,11 +3615,318 @@
  %7D);%0A  %7D
+%0A%0A  private static PostgresObjectType getObjectType(Map%3CString, ObjectType%3C? extends ObjectField%3E%3E objectTypes,%0A      String name) %7B%0A    return (PostgresObjectType) Optional.ofNullable(objectTypes.get(name))%0A        .orElseThrow(() -%3E invalidConfigurationException(%22Object type '%7B%7D' not found.%22, name));%0A  %7D
 %0A%7D%0A

Improve error handling (#500)