package io.aiven.klaw.helpers.db.rdbms;

import static org.springframework.beans.BeanUtils.copyProperties;

import io.aiven.klaw.dao.*;
import io.aiven.klaw.model.enums.ApiResultStatus;
import io.aiven.klaw.model.enums.EntityType;
import io.aiven.klaw.model.enums.RequestStatus;
import io.aiven.klaw.repository.*;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class InsertDataJdbc {

  public static final int DEFAULT_REQ_ID_NUMBER = 1001;

  @Autowired(required = false)
  private UserInfoRepo userInfoRepo;

  @Autowired(required = false)
  private RegisterInfoRepo registerInfoRepo;

  @Autowired(required = false)
  private TeamRepo teamRepo;

  @Autowired(required = false)
  private EnvRepo envRepo;

  @Autowired(required = false)
  private KwClusterRepo kwClusterRepo;

  @Autowired(required = false)
  private ActivityLogRepo activityLogRepo;

  @Autowired(required = false)
  private AclRequestsRepo aclRequestsRepo;

  @Autowired(required = false)
  private TopicRepo topicRepo;

  @Autowired(required = false)
  private OperationalRequestsRepo operationalRequestsRepo;

  @Autowired(required = false)
  private KwKafkaConnectorRepo kafkaConnectorRepo;

  @Autowired(required = false)
  private AclRepo aclRepo;

  @Autowired(required = false)
  private TenantRepo tenantRepo;

  @Autowired(required = false)
  private TopicRequestsRepo topicRequestsRepo;

  @Autowired(required = false)
  private KwKafkaConnectorRequestsRepo kafkaConnectorRequestsRepo;

  @Autowired(required = false)
  private SchemaRequestRepo schemaRequestRepo;

  @Autowired(required = false)
  private MessageSchemaRepo messageSchemaRepo;

  @Autowired(required = false)
  private ProductDetailsRepo productDetailsRepo;

  @Autowired(required = false)
  private KwPropertiesRepo kwPropertiesRepo;

  @Autowired(required = false)
  private KwRolesPermsRepo kwRolesPermsRepo;

  @Autowired(required = false)
  private KwMetricsRepo metricsRepo;

  @Autowired(required = false)
  private KwEntitySequenceRepo kwEntitySequenceRepo;

  @Autowired private SelectDataJdbc jdbcSelectHelper;

  public InsertDataJdbc() {}

  public synchronized Map<String, String> insertIntoRequestTopic(TopicRequest topicRequest) {
    log.debug("insertIntoRequestTopic {}", topicRequest);

    Map<String, String> hashMap = new HashMap<>();
    Integer topicId;
    if (topicRequest.getTopicid() == null) {
      topicId = getNextTopicRequestId("TOPIC_REQ_ID", topicRequest.getTenantId());
    } else {
      topicId = topicRequest.getTopicid();
    }
    hashMap.put("topicId", "" + topicId);

    topicRequest.setTopicid(topicId);

    topicRequest.setRequestStatus(RequestStatus.CREATED.value);
    topicRequest.setRequesttime((new Timestamp(System.currentTimeMillis())));
    topicRequestsRepo.save(topicRequest);

    hashMap.put("result", ApiResultStatus.SUCCESS.value);

    return hashMap;
  }

  public synchronized Map<String, String> insertIntoRequestConnector(
      KafkaConnectorRequest connectorRequest) {
    log.debug("insertIntoRequestConnector {}", connectorRequest);

    Map<String, String> hashMap = new HashMap<>();
    Integer topicId = getNextConnectorRequestId("CONNECTOR_REQ_ID", connectorRequest.getTenantId());
    hashMap.put("connectorId", "" + topicId);

    connectorRequest.setConnectorId(topicId);

    connectorRequest.setRequestStatus(RequestStatus.CREATED.value);
    connectorRequest.setRequesttime((new Timestamp(System.currentTimeMillis())));
    kafkaConnectorRequestsRepo.save(connectorRequest);

    hashMap.put("result", ApiResultStatus.SUCCESS.value);

    return hashMap;
  }

  public synchronized CRUDResponse<Topic> insertIntoTopicSOT(List<Topic> topics) {
    Set<Integer> existingTopicIds = new HashSet<>();
    topicRepo
        .findAllById(
            topics.stream()
                .filter(t -> !t.isExistingTopic())
                .map(t -> new TopicID(t.getTopicid(), t.getTenantId()))
                .collect(Collectors.toList()))
        .forEach(entry -> existingTopicIds.add(entry.getTopicid()));

    for (Topic topic : topics) {
      if (existingTopicIds.contains(topic.getTopicid())) {
        log.debug("insertIntoTopicSOT {} update topic Id", topic.getTopicname());
        topic.setTopicid(getNextTopicRequestId("TOPIC_ID", topic.getTenantId()));
      }
      log.debug("insertIntoTopicSOT {}", topic.getTopicname());
      topicRepo.save(topic);
    }

    return CRUDResponse.ok(topics);
  }

  public synchronized String insertIntoConnectorSOT(
      List<KwKafkaConnector> kafkaConnectors, boolean isSyncTopics) {

    kafkaConnectors.forEach(
        connector -> {
          log.debug("insertIntoConnectorSOT {}", connector.getConnectorName());
          if (!connector.isExistingConnector()) {
            KwKafkaConnectorID kwKafkaConnectorID = new KwKafkaConnectorID();
            kwKafkaConnectorID.setConnectorId(connector.getConnectorId());
            kwKafkaConnectorID.setTenantId(connector.getTenantId());

            if (kafkaConnectorRepo.existsById(kwKafkaConnectorID)) {
              connector.setConnectorId(
                  getNextConnectorRequestId("CONNECTOR_ID", connector.getTenantId()));
            }
          }
          kafkaConnectorRepo.save(connector);
        });

    return ApiResultStatus.SUCCESS.value;
  }

  private String insertIntoActivityLog(ActivityLog activityLog) {
    log.debug("insertIntoActivityLog {}", activityLog.getActivityName());
    activityLogRepo.save(activityLog);

    return ApiResultStatus.SUCCESS.value;
  }

  synchronized Map<String, String> insertIntoRequestAcl(AclRequests aclReq) {
    log.debug("insertIntoRequestAcl {}", aclReq.getTopicname());
    Map<String, String> hashMap = new HashMap<>();
    if (aclReq.getReq_no() == null) {
      Integer aclId = getNextAclRequestId(aclReq.getTenantId());
      hashMap.put("aclId", "" + aclId);

      if (aclReq.getRequestOperationType() != null) {
        aclReq.setReq_no(aclId);
      }
    }
    // Set foreign Keys for when you have approvals for the kwaclapprovals table
    if (aclReq.getApprovals() != null) {
      AclRequests parent = new AclRequests();
      parent.setReq_no(aclReq.getReq_no());
      parent.setTenantId(aclReq.getTenantId());
      for (AclApproval req : aclReq.getApprovals()) {
        req.setParent(parent);
      }
    }

    if (aclReq.getApprovals() != null) {
      AclRequests parent = new AclRequests();
      parent.setReq_no(aclReq.getReq_no());
      parent.setTenantId(aclReq.getTenantId());
      for (AclApproval req : aclReq.getApprovals()) {
        req.setParent(parent);
      }
    }

    aclReq.setRequestStatus(RequestStatus.CREATED.value);
    aclReq.setRequesttime(new Timestamp(System.currentTimeMillis()));
    final UserInfo userInfo = jdbcSelectHelper.selectUserInfo(aclReq.getRequestor());
    aclReq.setRequestingteam(userInfo.getTeamId());
    aclRequestsRepo.save(aclReq);

    hashMap.put("result", ApiResultStatus.SUCCESS.value);
    return hashMap;
  }

  synchronized Map<String, String> insertIntoOperationalRequests(
      OperationalRequest operationalRequest) {
    log.debug("insertIntoOperationalRequests {}", operationalRequest.getTopicname());
    Map<String, String> hashMap = new HashMap<>();

    Integer nextOperationalRequestId =
        getNextOperationalRequestId(operationalRequest.getTenantId());
    hashMap.put("reqId", "" + nextOperationalRequestId);
    operationalRequest.setReqId(nextOperationalRequestId);

    operationalRequest.setRequestStatus(RequestStatus.CREATED.value);
    operationalRequest.setRequesttime(new Timestamp(System.currentTimeMillis()));
    operationalRequestsRepo.save(operationalRequest);

    hashMap.put("result", ApiResultStatus.SUCCESS.value);
    return hashMap;
  }

  public synchronized String insertIntoAclsSOT(List<Acl> acls, boolean isSyncAcls) {

    acls.forEach(
        acl -> {
          log.debug("insertIntoAclsSOT {}", acl.getTopicname());
          if (acl.getReq_no() == null) {
            acl.setReq_no(getNextAclId(acl.getTenantId()));
          }
          aclRepo.save(acl);
        });
    return ApiResultStatus.SUCCESS.value;
  }

  public synchronized String insertIntoRequestSchema(SchemaRequest schemaRequest) {
    log.debug("insertIntoRequestSchema {}", schemaRequest.getTopicname());

    if (schemaRequest.getReq_no() == null) {
      schemaRequest.setReq_no(getNextSchemaRequestId("SCHEMA_REQ_ID", schemaRequest.getTenantId()));
    }

    schemaRequest.setSchemafull(schemaRequest.getSchemafull().trim());
    schemaRequest.setRequestStatus(RequestStatus.CREATED.value);
    schemaRequest.setRequesttime(new Timestamp(System.currentTimeMillis()));

    schemaRequestRepo.save(schemaRequest);

    return ApiResultStatus.SUCCESS.value;
  }

  public synchronized String insertIntoMessageSchemaSOT(List<MessageSchema> schemas) {

    for (MessageSchema mSchema : schemas) {
      log.debug("insertIntoMessageSchemaSOT {}", mSchema.getTopicname());
      if (mSchema.getReq_no() == null) {
        mSchema.setReq_no(getNextSchemaRequestId("SCHEMA_ID", mSchema.getTenantId()));
      }
      messageSchemaRepo.save(mSchema);
    }
    return ApiResultStatus.SUCCESS.value;
  }

  public String insertIntoUsers(UserInfo userInfo) {
    log.debug("insertIntoUsers {}", userInfo.getUsername());
    Optional<UserInfo> userExists = userInfoRepo.findById(userInfo.getUsername());
    if (userExists.isPresent()) {
      return "Failure. User already exists";
    }

    userInfoRepo.save(userInfo);
    return ApiResultStatus.SUCCESS.value;
  }

  public String insertIntoTeams(Team team) {
    log.debug("insertIntoTeams {}", team.getTeamname());
    Integer teamId = getNextSeqIdAndUpdate(EntityType.TEAM.name(), team.getTenantId());
    if (teamId == null) { // check for INFRATEAM, STAGINTEAM
      teamId = DEFAULT_REQ_ID_NUMBER;
      insertIntoKwEntitySequence(EntityType.TEAM.name(), teamId + 1, team.getTenantId());
    }
    TeamID teamID = new TeamID(teamId, team.getTenantId());
    team.setTeamId(teamId);

    Optional<Team> teamExists = teamRepo.findById(teamID);
    if (teamExists.isPresent()) {
      return "Failure. Team already exists";
    }

    team.setApp("");
    teamRepo.save(team);
    return ApiResultStatus.SUCCESS.value;
  }

  public Integer getNextTeamId(int tenantId) {
    Integer teamId = teamRepo.getNextTeamId(tenantId);
    if (teamId == null) {
      return DEFAULT_REQ_ID_NUMBER;
    } else {
      return teamId + 1;
    }
  }

  public String addNewEnv(Env env) {
    log.debug("Insert or Update Env {}", env.getName());

    Integer lastId;
    if (env.getId() == null) {
      lastId = getNextSeqIdAndUpdate(EntityType.ENVIRONMENT.name(), env.getTenantId());
      env.setId(lastId + "");
    }

    envRepo.save(env);
    return ApiResultStatus.SUCCESS.value;
  }

  public void insertIntoKwEntitySequence(String entityName, int maxId, int tenantId) {
    KwEntitySequence kwEntitySequence = new KwEntitySequence();
    kwEntitySequence.setEntityName(entityName);
    kwEntitySequence.setSeqId(maxId);
    kwEntitySequence.setTenantId(tenantId);
    kwEntitySequenceRepo.save(kwEntitySequence);
  }

  public String insertIntoClusters(KwClusters kwClusters) {
    log.debug("insertIntoClusters {}", kwClusters.getClusterName());

    Integer lastId;
    if (kwClusters.getClusterId() == null) {
      lastId = getNextSeqIdAndUpdate(EntityType.CLUSTER.name(), kwClusters.getTenantId());
      kwClusters.setClusterId(lastId);
    }
    kwClusterRepo.save(kwClusters);

    return ApiResultStatus.SUCCESS.value;
  }

  public boolean hasSequence(String entityName, int tenantId) {
    return kwEntitySequenceRepo.existsByEntityNameAndTenantId(entityName, tenantId);
  }

  public Integer getNextSeqIdAndUpdate(String entityName, int tenantId) {
    List<KwEntitySequence> kwEntitySequenceList =
        kwEntitySequenceRepo.findAllByEntityNameAndTenantId(entityName, tenantId);
    if (!kwEntitySequenceList.isEmpty()) {
      Integer lastId = kwEntitySequenceList.get(0).getSeqId();
      insertIntoKwEntitySequence(entityName, lastId + 1, tenantId);
      return lastId;
    } else {
      // only for entityName TEAM as STAGING and INFRATEAM are added during startup
      return null;
    }
  }

  public String insertIntoRegisterUsers(RegisterUserInfo userInfo) {
    log.debug("insertIntoRegisterUsers {}", userInfo.getUsername());

    Optional<UserInfo> userNameExists = userInfoRepo.findById(userInfo.getUsername());
    if (userNameExists.isPresent()) return "Failure. User already exists";

    Optional<RegisterUserInfo> optionalUserRegistration =
        registerInfoRepo.findByUsername(userInfo.getUsername());
    if (optionalUserRegistration.isPresent()) {
      if ("APPROVED".equals(optionalUserRegistration.get().getStatus())) {
        // do nothing -- user is deleted
      } else if (!"STAGING".equals(optionalUserRegistration.get().getStatus())
          && !"PENDING".equals(optionalUserRegistration.get().getStatus())) {
        return "Failure. Registration already exists";
      } else {
        int id = optionalUserRegistration.get().getId();
        copyProperties(userInfo, optionalUserRegistration.get());
        optionalUserRegistration.get().setId(id);
        registerInfoRepo.save(optionalUserRegistration.get());
        return ApiResultStatus.SUCCESS.value;
      }
    }

    registerInfoRepo.save(userInfo);
    return ApiResultStatus.SUCCESS.value;
  }

  public Integer getNextAclRequestId(int tenantId) {
    Integer aclReqId = aclRequestsRepo.getNextAclRequestId(tenantId);
    if (aclReqId == null) {
      return DEFAULT_REQ_ID_NUMBER;
    } else {
      return aclReqId + 1;
    }
  }

  public Integer getNextOperationalRequestId(int tenantId) {
    Integer operationalRequestId = operationalRequestsRepo.getNextOperationalRequestId(tenantId);
    if (operationalRequestId == null) {
      return DEFAULT_REQ_ID_NUMBER;
    } else {
      return operationalRequestId + 1;
    }
  }

  public Integer getNextAclId(int tenantId) {
    Integer aclId = aclRepo.getNextAclId(tenantId);
    if (aclId == null) {
      return DEFAULT_REQ_ID_NUMBER;
    } else {
      return aclId + 1;
    }
  }

  public synchronized Integer getNextActivityLogRequestId(int tenantId) {
    Integer activityLogId = activityLogRepo.getNextActivityLogRequestId(tenantId);

    if (activityLogId == null) {
      return DEFAULT_REQ_ID_NUMBER;
    } else {
      return activityLogId + 1;
    }
  }

  public Integer getNextTopicRequestId(String idType, int tenantId) {
    Integer topicId = null;
    if ("TOPIC_REQ_ID".equals(idType)) {
      topicId = topicRequestsRepo.getNextTopicRequestId(tenantId);
    } else if ("TOPIC_ID".equals(idType)) {
      topicId = topicRepo.getNextTopicRequestId(tenantId);
    }

    return topicId == null ? DEFAULT_REQ_ID_NUMBER : topicId + 1;
  }

  public Integer getNextConnectorRequestId(String idType, int tenantId) {
    Integer topicId;
    if (idType.equals("CONNECTOR_REQ_ID")) {
      topicId = kafkaConnectorRequestsRepo.getNextConnectorRequestId(tenantId);
    } else if (idType.equals("CONNECTOR_ID")) {
      topicId = kafkaConnectorRepo.getNextConnectorRequestId(tenantId);
    } else {
      topicId = null;
    }

    if (topicId == null) {
      return DEFAULT_REQ_ID_NUMBER;
    } else {
      return topicId + 1;
    }
  }

  public Integer getNextSchemaRequestId(String idType, int tenantId) {
    Integer schemaReqId = null;
    if ("SCHEMA_REQ_ID".equals(idType)) {
      schemaReqId = schemaRequestRepo.getNextSchemaRequestId(tenantId);
    } else if ("SCHEMA_ID".equals(idType)) {
      schemaReqId = messageSchemaRepo.getNextSchemaId(tenantId);
    }

    return schemaReqId == null ? DEFAULT_REQ_ID_NUMBER : schemaReqId + 1;
  }

  public String addNewTenant(KwTenants kwTenants) {
    if (kwTenants.getTenantId() != null) {
      Optional<KwTenants> tenantExists = tenantRepo.findById(kwTenants.getTenantId());
      if (tenantExists.isPresent()) {
        tenantRepo.save(kwTenants);
        return ApiResultStatus.SUCCESS.value;
      }
    }

    Integer maxTenantId = tenantRepo.getMaxTenantId();
    if (maxTenantId == null) {
      maxTenantId = 101;
    } else {
      maxTenantId = maxTenantId + 1;
    }
    kwTenants.setTenantId(maxTenantId);

    tenantRepo.save(kwTenants);
    return ApiResultStatus.SUCCESS.value;
  }

  public String registerUserForAD(RegisterUserInfo newUser) {
    registerInfoRepo.save(newUser);
    return ApiResultStatus.SUCCESS.value;
  }

  public String insertMetrics(KwMetrics kwMetrics) {
    Integer metricsId = metricsRepo.getNextId();
    if (metricsId == null) {
      metricsId = DEFAULT_REQ_ID_NUMBER;
    } else {
      metricsId += 1;
    }

    kwMetrics.setMetricsId(metricsId);

    metricsRepo.save(kwMetrics);
    return ApiResultStatus.SUCCESS.value;
  }

  public String insertDefaultKwProperties(List<KwProperties> kwPropertiesList) {
    kwPropertiesRepo.saveAll(kwPropertiesList);
    return ApiResultStatus.SUCCESS.value;
  }

  public String insertDefaultRolesPermissions(List<KwRolesPermissions> kwRolesPermissionsList) {

    for (KwRolesPermissions kwRolesPermissions : kwRolesPermissionsList) {
      kwRolesPermissions.setId(getNextRolePermissionId(kwRolesPermissions.getTenantId()));
      kwRolesPermsRepo.save(kwRolesPermissions);
    }

    return ApiResultStatus.SUCCESS.value;
  }

  public Integer getNextRolePermissionId(int tenantId) {
    Integer maxId = kwRolesPermsRepo.getMaxRolePermissionId(tenantId);
    if (maxId == null) {
      maxId = 1;
    }

    return maxId + 1;
  }

  public String insertProductDetails(ProductDetails productDetails) {
    productDetailsRepo.save(productDetails);
    return ApiResultStatus.SUCCESS.value;
  }

  public void insertIntoActivityLog(
      String requestType,
      int tenantId,
      String operationType,
      int teamId,
      String details,
      String envId,
      String requestor) {
    ActivityLog activityLog = new ActivityLog();
    activityLog.setReq_no(getNextActivityLogRequestId(tenantId));
    activityLog.setActivityName(requestType + " Request");
    activityLog.setActivityType(operationType);
    activityLog.setActivityTime(new Timestamp(System.currentTimeMillis()));
    activityLog.setTeamId(teamId);
    activityLog.setDetails(details);
    activityLog.setUser(requestor);
    activityLog.setEnv(envId);
    activityLog.setTenantId(tenantId);
    insertIntoActivityLog(activityLog);
  }
}
