package com.ffii.fpsms.modules.user.service;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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

import com.ffii.core.exception.NotFoundException;
import com.ffii.core.support.AbstractBaseEntityService;
import com.ffii.core.support.JdbcDao;
import com.ffii.core.utils.JsonUtils;
import com.ffii.core.utils.Params;
import com.ffii.fpsms.modules.common.SecurityUtils;
import com.ffii.fpsms.modules.common.service.AuditLogService;
import com.ffii.fpsms.modules.user.entity.Group;
import com.ffii.fpsms.modules.user.entity.GroupRepository;
import com.ffii.fpsms.modules.user.req.SaveGroupReq;

import jakarta.persistence.Table;
import jakarta.validation.Valid;


@Service
public class GroupService extends AbstractBaseEntityService<Group, Long, GroupRepository> {

	@Autowired
	private AuditLogService auditLogService;

	public GroupService(JdbcDao jdbcDao, GroupRepository repository) {
		super(jdbcDao, repository);
	}

	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");
		}

		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 {
			instance = new Group();
		}
		BeanUtils.copyProperties(req, instance);

		String tableName = instance.getClass().getAnnotation(Table.class).name();
		StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id");
		String oldValueJson = null;
		String newValueJson = null;

		if (instance != null && instance.getId() != null && instance.getId() > 0) {
			oldValueJson = JsonUtils.toJsonString(jdbcDao.queryForMap(sql.toString(), Map.of("id", instance.getId())).orElseThrow(NotFoundException::new));
		}
		
		instance = saveAndFlush(instance);
		Long id = instance.getId();

		List<Map<String, Long>> userBatchInsertValues = req.getAddUserIds().stream()
				.map(userId -> Map.of("groupId", id, "userId", userId))
				.collect(Collectors.toList());
		List<Map<String, Long>> 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, Long>> authBatchInsertValues = req.getAddAuthIds().stream()
				.map(authId -> Map.of("groupId", id, "authId", authId))
				.collect(Collectors.toList());
		List<Map<String, Long>> 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);
		}

		if (instance != null && instance.getId() != null && instance.getId() > 0) {
			newValueJson = JsonUtils.toJsonString(jdbcDao.queryForMap(sql.toString(), Map.of("id", instance.getId())).orElseThrow(NotFoundException::new));
		}

		auditLogService.save(
				tableName,
				id,
				SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null,
				new Date(),
				oldValueJson,
				newValueJson);
		return instance;
	}

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

	public List<Integer> listGroupUserId(Long 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));
	}

}
