package com.ffii.core.user.service;

import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.validation.Valid;

import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ffii.core.exception.NotFoundException;
import com.ffii.core.exception.UnprocessableEntityException;
import com.ffii.core.support.AbstractBaseEntityService;
import com.ffii.core.support.JdbcDao;
import com.ffii.core.user.dao.GroupDao;
import com.ffii.core.user.entity.Group;
import com.ffii.core.user.req.SaveGroupReq;
import com.ffii.core.utils.Params;

@Service
public class GroupService extends AbstractBaseEntityService<Group, Integer, GroupDao> {

  public GroupService(JdbcDao jdbcDao, GroupDao dao) {
    super(jdbcDao, dao);
  }

  public List<Map<String, Object>> search(Map<String, Object> args) {
    StringBuilder sql = new StringBuilder("SELECT"
        + " g.*"
        + " FROM `group` g"
        + " WHERE g.deleted = FALSE");

    if (args != null) {
      if (args.containsKey(Params.QUERY))
        sql.append(" AND (g.name LIKE :query)");
      if (args.containsKey(Params.ID))
        sql.append(" AND g.id = :id");
      if (args.containsKey(Params.NAME))
        sql.append(" AND g.name LIKE :name");
      if (args.containsKey("checkNameIsDuplicate"))
        sql.append(" AND BINARY g.name = :checkNameIsDuplicate AND g.deleted = 0 ");
    }

    sql.append(" ORDER BY g.name");

    return jdbcDao.queryForList(sql.toString(), args);
  }

  public List<Map<String, Object>> searchForCombo(Map<String, Object> args) {
    StringBuilder sql = new StringBuilder("SELECT"
        + " g.id,"
        + " g.name"
        + " FROM `group` g"
        + " WHERE g.deleted = FALSE");

    if (args != null) {
      if (args.containsKey(Params.QUERY))
        sql.append(" AND (g.name LIKE :query)");
      if (args.containsKey(Params.ID))
        sql.append(" AND g.id = :id");
    }

    sql.append(" ORDER BY g.name");

    return jdbcDao.queryForList(sql.toString(), args);
  }

  @Transactional(rollbackFor = Exception.class)
  public void delete(Group instance) {
    Map<String, Object> args = Map.of("groupId", instance.getId());
    jdbcDao.executeUpdate("DELETE FROM user_group WHERE groupId = :groupId;", args);
    jdbcDao.executeUpdate("DELETE FROM group_authority WHERE groupId = :groupId;", args);
    markDelete(instance);
  }

  @Transactional(rollbackFor = Exception.class)
  public Group saveOrUpdate(@Valid SaveGroupReq req) {
    Group instance;
    if (req.getId() != null) {
      instance = find(req.getId()).orElseThrow(NotFoundException::new);
    } else {
      List<Map<String, Object>> list = search(Map.of("checkNameIsDuplicate", req.getName()));
      if (list != null && list.size() > 0) {
        throw new UnprocessableEntityException(
            MessageFormat.format("Duplicate entry {0} for key 'name'", req.getName()));
      }
      instance = new Group();
    }
    BeanUtils.copyProperties(req, instance);
    instance = save(instance);
    // try {
    // } catch (DataIntegrityViolationException e) {
    // throw new BadRequestException(MessageFormat.format("Duplicate entry {0} for
    // key 'name'", req.getName()));
    // }

    int id = instance.getId();

    List<Map<String, Integer>> userBatchInsertValues = req.getAddUserIds().stream()
        .map(userId -> Map.of("groupId", id, "userId", userId))
        .collect(Collectors.toList());
    List<Map<String, Integer>> userBatchDeleteValues = req.getRemoveUserIds().stream()
        .map(userId -> Map.of("groupId", id, "userId", userId))
        .collect(Collectors.toList());

    if (!userBatchInsertValues.isEmpty()) {
      jdbcDao.batchUpdate(
          "INSERT IGNORE INTO user_group (groupId,userId)"
              + " VALUES (:groupId, :userId)",
          userBatchInsertValues);
    }
    if (!userBatchDeleteValues.isEmpty()) {
      jdbcDao.batchUpdate(
          "DELETE FROM user_group"
              + " WHERE groupId = :groupId AND userId = :userId",
          userBatchDeleteValues);
    }

    List<Map<String, Integer>> authBatchInsertValues = req.getAddAuthIds().stream()
        .map(authId -> Map.of("groupId", id, "authId", authId))
        .collect(Collectors.toList());
    List<Map<String, Integer>> authBatchDeleteValues = req.getRemoveAuthIds().stream()
        .map(authId -> Map.of("groupId", id, "authId", authId))
        .collect(Collectors.toList());

    if (!authBatchInsertValues.isEmpty()) {
      jdbcDao.batchUpdate(
          "INSERT IGNORE INTO group_authority (groupId, authId)"
              + " VALUES (:groupId, :authId)",
          authBatchInsertValues);
    }
    if (!authBatchDeleteValues.isEmpty()) {
      jdbcDao.batchUpdate(
          "DELETE FROM group_authority"
              + " WHERE groupId = :groupId AND authId = :authId",
          authBatchDeleteValues);
    }

    return instance;
  }

  public List<Integer> listGroupAuthId(int id) {
    return jdbcDao.queryForInts(
        "SELECT"
            + " ga.authId"
            + " FROM group_authority ga"
            + " WHERE ga.groupId = :id",
        Map.of(Params.ID, id));
  }

  public List<Integer> listGroupUserId(int id) {
    return jdbcDao.queryForInts(
        "SELECT"
            + " gu.userId"
            + " FROM user_group gu"
            + " INNER JOIN user u ON u.deleted = FALSE AND gu.userId = u.id"
            + " WHERE gu.groupId = :id",
        Map.of(Params.ID, id));
  }

  // here
  public boolean existsActiveUser(int id) {
    return jdbcDao.queryForBoolean(" select exists"
        + " (select *"
        + " from user_group ug"
        + " inner join `user` u on u.id = ug.userId"
        + " where ug.groupId = :id"
        + " and u.deleted = 0)", Map.of("id", id));
  }
}
