package cn.cordys.crm.customer.service;

import cn.cordys.aspectj.annotation.OperationLog;
import cn.cordys.aspectj.constants.LogModule;
import cn.cordys.aspectj.constants.LogType;
import cn.cordys.aspectj.context.OperationLogContext;
import cn.cordys.aspectj.dto.LogContextInfo;
import cn.cordys.common.constants.BusinessModuleField;
import cn.cordys.common.constants.FormKey;
import cn.cordys.common.dto.BasePageRequest;
import cn.cordys.common.dto.condition.CombineSearch;
import cn.cordys.common.exception.GenericException;
import cn.cordys.common.uid.IDGenerator;
import cn.cordys.common.util.BeanUtils;
import cn.cordys.common.util.JSON;
import cn.cordys.common.util.Translator;
import cn.cordys.common.utils.RecycleConditionUtils;
import cn.cordys.crm.customer.domain.*;
import cn.cordys.crm.customer.dto.CustomerPoolDTO;
import cn.cordys.crm.customer.dto.CustomerPoolFieldConfigDTO;
import cn.cordys.crm.customer.dto.CustomerPoolPickRuleDTO;
import cn.cordys.crm.customer.dto.CustomerPoolRecycleRuleDTO;
import cn.cordys.crm.customer.dto.request.CustomerPoolAddRequest;
import cn.cordys.crm.customer.dto.request.CustomerPoolUpdateRequest;
import cn.cordys.crm.customer.mapper.ExtCustomerPoolMapper;
import cn.cordys.crm.system.constants.RecycleConditionColumnKey;
import cn.cordys.crm.system.constants.RecycleConditionOperator;
import cn.cordys.crm.system.constants.RecycleConditionScopeKey;
import cn.cordys.crm.system.domain.User;
import cn.cordys.crm.system.dto.RuleConditionDTO;
import cn.cordys.crm.system.dto.field.base.BaseField;
import cn.cordys.crm.system.service.ModuleFormCacheService;
import cn.cordys.crm.system.service.UserExtendService;
import cn.cordys.mybatis.BaseMapper;
import cn.cordys.mybatis.lambda.LambdaQueryWrapper;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
@Transactional(rollbackFor = Exception.class)
public class CustomerPoolService {

    @Resource
    private BaseMapper<User> userMapper;
    @Resource
    private BaseMapper<Customer> customerMapper;
    @Resource
    private BaseMapper<CustomerPool> customerPoolMapper;
    @Resource
    private BaseMapper<CustomerPoolHiddenField> customerPoolHiddenFieldMapper;
    @Resource
    private BaseMapper<CustomerPoolPickRule> customerPoolPickRuleMapper;
    @Resource
    private BaseMapper<CustomerPoolRecycleRule> customerPoolRecycleRuleMapper;
    @Resource
    private ExtCustomerPoolMapper extCustomerPoolMapper;
    @Resource
    private UserExtendService userExtendService;
    @Resource
    private ModuleFormCacheService moduleFormCacheService;

    /**
     * 分页获取公海池
     *
     * @param request 分页参数
     *
     * @return 公海池列表
     */
    public List<CustomerPoolDTO> page(BasePageRequest request, String organizationId) {
        List<CustomerPoolDTO> pools = extCustomerPoolMapper.list(request, organizationId);
        if (CollectionUtils.isEmpty(pools)) {
            return new ArrayList<>();
        }

        List<String> userIds = pools.stream().flatMap(pool -> Stream.of(pool.getCreateUser(), pool.getUpdateUser())).toList();
        List<User> createOrUpdateUsers = userMapper.selectByIds(userIds.toArray(new String[0]));
        Map<String, String> userMap = createOrUpdateUsers.stream()
                .collect(Collectors.toMap(User::getId, User::getName));

        List<String> poolIds = pools.stream()
                .map(CustomerPoolDTO::getId)
                .toList();

        var pickRuleWrapper = new LambdaQueryWrapper<CustomerPoolPickRule>();
        pickRuleWrapper.in(CustomerPoolPickRule::getPoolId, poolIds);

        List<CustomerPoolPickRule> pickRules = customerPoolPickRuleMapper.selectListByLambda(pickRuleWrapper);
        Map<String, CustomerPoolPickRule> pickRuleMap = pickRules.stream()
                .collect(Collectors.toMap(CustomerPoolPickRule::getPoolId, pickRule -> pickRule));

        var recycleRuleWrapper = new LambdaQueryWrapper<CustomerPoolRecycleRule>();
        recycleRuleWrapper.in(CustomerPoolRecycleRule::getPoolId, poolIds);

        List<CustomerPoolRecycleRule> recycleRules = customerPoolRecycleRuleMapper.selectListByLambda(recycleRuleWrapper);
        Map<String, CustomerPoolRecycleRule> recycleRuleMap = recycleRules.stream()
                .collect(Collectors.toMap(CustomerPoolRecycleRule::getPoolId, recycleRule -> recycleRule));

        Map<String, List<CustomerPoolHiddenField>> hiddenFieldMap = getCustomerPoolHiddenFieldByPoolIds(poolIds)
                .stream()
                .collect(Collectors.groupingBy(CustomerPoolHiddenField::getPoolId));

        List<BaseField> fields = moduleFormCacheService.getBusinessFormConfig(FormKey.CUSTOMER.getKey(), organizationId).getFields();

        pools.forEach(pool -> {
            pool.setMembers(userExtendService.getScope(JSON.parseArray(pool.getScopeId(), String.class)));
            pool.setOwners(userExtendService.getScope(JSON.parseArray(pool.getOwnerId(), String.class)));
            pool.setCreateUserName(userMap.get(pool.getCreateUser()));
            pool.setUpdateUserName(userMap.get(pool.getUpdateUser()));

            var pickRule = new CustomerPoolPickRuleDTO();
            BeanUtils.copyBean(pickRule, pickRuleMap.get(pool.getId()));
            var recycleRule = new CustomerPoolRecycleRuleDTO();
            CustomerPoolRecycleRule customerPoolRecycleRule = recycleRuleMap.get(pool.getId());
            BeanUtils.copyBean(recycleRule, customerPoolRecycleRule);
            recycleRule.setConditions(JSON.parseArray(customerPoolRecycleRule.getCondition(), RuleConditionDTO.class));
            delOldTime(recycleRule);
            pool.setPickRule(pickRule);
            pool.setRecycleRule(recycleRule);

            Set<String> hiddenFieldIds;
            if (hiddenFieldMap.get(pool.getId()) != null) {
                hiddenFieldIds = hiddenFieldMap.get(pool.getId()).stream()
                        .map(CustomerPoolHiddenField::getFieldId)
                        .collect(Collectors.toSet());
            } else {
                hiddenFieldIds = Set.of();
            }

            pool.setFieldConfigs(getFieldConfigs(fields, hiddenFieldIds));
        });

        return pools;
    }

    private void delOldTime(CustomerPoolRecycleRuleDTO recycleRule) {
        recycleRule.getConditions().forEach(condition -> {
            if (Strings.CS.equals(condition.getOperator(), RecycleConditionOperator.DYNAMICS.name())) {
                String[] split = condition.getValue().split(",");
                if (StringUtils.isNotBlank(condition.getValue()) && split.length == 2) {
                    String dateValue = split[0];
                    String dateUnit = split[1];
                    dateUnit = switch (dateUnit) {
                        case "day" -> "BEFORE_DAY";
                        case "month" -> "BEFORE_MONTH";
                        case "week" -> "BEFORE_WEEK";
                        default -> dateUnit;
                    };
                    condition.setValue("CUSTOM," + dateValue + "," + dateUnit);
                }
            }
        });
    }


    public List<CustomerPoolFieldConfigDTO> getFieldConfigs(List<BaseField> fields, Set<String> hiddenFieldIds) {
        return fields.stream().map(field -> {
            var hiddenFieldDTO = new CustomerPoolFieldConfigDTO();
            hiddenFieldDTO.setFieldId(field.getId());
            hiddenFieldDTO.setFieldName(field.getName());
            hiddenFieldDTO.setEnable(!hiddenFieldIds.contains(field.getId()));
            hiddenFieldDTO.setEditable(!Strings.CS.equals(field.getInternalKey(), BusinessModuleField.CUSTOMER_NAME.getKey()));
            return hiddenFieldDTO;
        }).toList();
    }

    public List<CustomerPoolHiddenField> getCustomerPoolHiddenFieldByPoolIds(List<String> poolIds) {
        var hiddenFieldWrapper = new LambdaQueryWrapper<CustomerPoolHiddenField>();
        hiddenFieldWrapper.in(CustomerPoolHiddenField::getPoolId, poolIds);
        return customerPoolHiddenFieldMapper.selectListByLambda(hiddenFieldWrapper);
    }

    /**
     * 新增公海池
     *
     * @param request       请求参数
     * @param currentUserId 当前用户ID
     */
    @OperationLog(module = LogModule.SYSTEM_MODULE, type = LogType.ADD)
    public void add(CustomerPoolAddRequest request, String currentUserId, String organizationId) {
        var pool = new CustomerPool();
        BeanUtils.copyBean(pool, request);
        pool.setId(IDGenerator.nextStr());
        pool.setOrganizationId(organizationId);
        pool.setOwnerId(JSON.toJSONString(request.getOwnerIds()));
        pool.setScopeId(JSON.toJSONString(request.getScopeIds()));
        pool.setCreateTime(System.currentTimeMillis());
        pool.setCreateUser(currentUserId);
        pool.setUpdateTime(System.currentTimeMillis());
        pool.setUpdateUser(currentUserId);
        customerPoolMapper.insert(pool);
        var pickRule = new CustomerPoolPickRule();
        BeanUtils.copyBean(pickRule, request.getPickRule());
        pickRule.setId(IDGenerator.nextStr());
        pickRule.setPoolId(pool.getId());
        pickRule.setCreateUser(currentUserId);
        pickRule.setCreateTime(System.currentTimeMillis());
        pickRule.setUpdateUser(currentUserId);
        pickRule.setUpdateTime(System.currentTimeMillis());
        customerPoolPickRuleMapper.insert(pickRule);
        var recycleRule = new CustomerPoolRecycleRule();
        BeanUtils.copyBean(recycleRule, request.getRecycleRule());
        recycleRule.setId(IDGenerator.nextStr());
        try {
            recycleRule.setCondition(JSON.toJSONString(request.getRecycleRule().getConditions()));
        } catch (Exception e) {
            throw new GenericException(Translator.get("customer_rule_condition_error"));
        }
        recycleRule.setPoolId(pool.getId());
        recycleRule.setCreateUser(currentUserId);
        recycleRule.setCreateTime(System.currentTimeMillis());
        recycleRule.setUpdateUser(currentUserId);
        recycleRule.setUpdateTime(System.currentTimeMillis());
        customerPoolRecycleRuleMapper.insert(recycleRule);

        batchInsertCustomerPoolHiddenFields(pool.getId(), request.getHiddenFieldIds());

        // 添加日志上下文
        OperationLogContext.setContext(LogContextInfo.builder()
                .modifiedValue(pool)
                .resourceId(pool.getId())
                .resourceName(Translator.get("module.customer.pool.setting"))
                .build());
    }

    /**
     * 修改公海池
     *
     * @param request       请求参数
     * @param currentUserId 当前用户ID
     */
    @OperationLog(module = LogModule.SYSTEM_MODULE, type = LogType.UPDATE, resourceId = "{#request.id}")
    public void update(CustomerPoolUpdateRequest request, String currentUserId, String organizationId) {
        CustomerPool originCustomerPool = checkPoolExist(request.getId());
        var pool = new CustomerPool();
        BeanUtils.copyBean(pool, request);
        pool.setOrganizationId(organizationId);
        pool.setOwnerId(JSON.toJSONString(request.getOwnerIds()));
        pool.setScopeId(JSON.toJSONString(request.getScopeIds()));
        pool.setUpdateTime(System.currentTimeMillis());
        pool.setUpdateUser(currentUserId);
        customerPoolMapper.update(pool);
        var pickRule = new CustomerPoolPickRule();
        BeanUtils.copyBean(pickRule, request.getPickRule());
        pickRule.setPoolId(pool.getId());
        pickRule.setUpdateUser(currentUserId);
        pickRule.setUpdateTime(System.currentTimeMillis());
        extCustomerPoolMapper.updatePickRule(pickRule);
        var recycleRule = new CustomerPoolRecycleRule();
        BeanUtils.copyBean(recycleRule, request.getRecycleRule());
        recycleRule.setPoolId(pool.getId());
        try {
            recycleRule.setCondition(JSON.toJSONString(request.getRecycleRule().getConditions()));
        } catch (Exception e) {
            throw new GenericException(Translator.get("customer_rule_condition_error"));
        }
        recycleRule.setUpdateUser(currentUserId);
        recycleRule.setUpdateTime(System.currentTimeMillis());
        extCustomerPoolMapper.updateRecycleRule(recycleRule);

        if (request.getHiddenFieldIds() != null) {
            deleteCustomerPoolHiddenFieldByPoolId(pool.getId());
            batchInsertCustomerPoolHiddenFields(pool.getId(), request.getHiddenFieldIds());
        }

        OperationLogContext.setContext(
                LogContextInfo.builder()
                        .resourceName(Translator.get("module.customer.pool.setting"))
                        .originalValue(originCustomerPool)
                        .modifiedValue(customerPoolMapper.selectByPrimaryKey(request.getId()))
                        .build()
        );
    }

    private void batchInsertCustomerPoolHiddenFields(String poolId, Set<String> fieldIds) {
        if (CollectionUtils.isEmpty(fieldIds)) {
            return;
        }
        List<CustomerPoolHiddenField> customerPoolHiddenFields = fieldIds.stream()
                .map(fieldId -> {
                    var customerPoolHiddenField = new CustomerPoolHiddenField();
                    customerPoolHiddenField.setFieldId(fieldId);
                    customerPoolHiddenField.setPoolId(poolId);
                    return customerPoolHiddenField;
                }).toList();
        customerPoolHiddenFieldMapper.batchInsert(customerPoolHiddenFields);
    }

    private void deleteCustomerPoolHiddenFieldByPoolId(String poolId) {
        var customerPoolHiddenField = new CustomerPoolHiddenField();
        customerPoolHiddenField.setPoolId(poolId);
        customerPoolHiddenFieldMapper.delete(customerPoolHiddenField);
    }

    /**
     * 公海池是否存在未领取线索
     *
     * @param id 线索池ID
     */
    public boolean checkNoPick(String id) {
        var wrapper = new LambdaQueryWrapper<Customer>();
        wrapper.eq(Customer::getPoolId, id)
                .eq(Customer::getInSharedPool, true);
        List<Customer> relations = customerMapper.selectListByLambda(wrapper);
        return CollectionUtils.isNotEmpty(relations);
    }

    /**
     * 删除公海池
     */
    @OperationLog(module = LogModule.SYSTEM_MODULE, type = LogType.DELETE, resourceId = "{#id}")
    public void delete(String id) {
        checkPoolExist(id);
        customerPoolMapper.deleteByPrimaryKey(id);
        var pickRule = new CustomerPoolPickRule();
        pickRule.setPoolId(id);
        customerPoolPickRuleMapper.delete(pickRule);
        var recycleRule = new CustomerPoolRecycleRule();
        recycleRule.setPoolId(id);
        customerPoolRecycleRuleMapper.delete(recycleRule);
        deleteCustomerPoolHiddenFieldByPoolId(id);

        // 设置操作对象
        OperationLogContext.setResourceName(Translator.get("module.customer.pool.setting"));
    }

    /**
     * 启用/禁用公海池
     *
     * @param id 线索池ID
     */
    @OperationLog(module = LogModule.SYSTEM_MODULE, type = LogType.UPDATE, resourceId = "{#id}")
    public void switchStatus(String id, String currentUserId) {
        CustomerPool pool = checkPoolExist(id);
        pool.setEnable(!pool.getEnable());
        pool.setUpdateTime(System.currentTimeMillis());
        pool.setUpdateUser(currentUserId);
        customerPoolMapper.updateById(pool);

        OperationLogContext.setContext(
                LogContextInfo.builder()
                        .resourceName(Translator.get("module.customer.pool.setting"))
                        .originalValue(pool)
                        .modifiedValue(customerPoolMapper.selectByPrimaryKey(id))
                        .build()
        );
    }

    /**
     * 校验公海池是否存在
     *
     * @param id 公海池ID
     *
     * @return 公海池
     */
    private CustomerPool checkPoolExist(String id) {
        CustomerPool pool = customerPoolMapper.selectByPrimaryKey(id);
        if (pool == null) {
            throw new GenericException(Translator.get("customer_pool_not_exist"));
        }
        return pool;
    }

    /**
     * 获取负责人默认公海ID
     *
     * @param ownerIds       负责人ID集合
     * @param organizationId 组织ID
     *
     * @return 默认公海
     */
    public Map<String, CustomerPool> getOwnersDefaultPoolMap(List<String> ownerIds, String organizationId) {
        var poolMap = new HashMap<String, CustomerPool>(4);
        List<CustomerPool> pools = extCustomerPoolMapper.getAllPool(organizationId);
        Map<String, List<String>> ownerScopeMap = userExtendService.getMultiScopeMap(ownerIds, organizationId);
        ownerIds.forEach(ownerId -> {
            List<CustomerPool> matchPools = matchMultiScope(ownerScopeMap.get(ownerId), pools);
            if (CollectionUtils.isEmpty(matchPools)) {
                // not found pool for owner
                return;
            }
            poolMap.put(ownerId, matchPools.getFirst());
        });

        return poolMap;
    }

    /**
     * 匹配多个范围的公海
     *
     * @param scopeIds 范围ID集合
     * @param pools    公海列表
     *
     * @return 命中范围的公海列表
     */
    public List<CustomerPool> matchMultiScope(List<String> scopeIds, List<CustomerPool> pools) {
        /*
         * 命中线索池任意范围即返回(默认按照创建时间作为优先级)
         */
        if (CollectionUtils.isEmpty(scopeIds) || CollectionUtils.isEmpty(pools)) {
            return new ArrayList<>();
        }
        return pools.stream()
                .filter(pool -> {
                    List<String> poolScopes = JSON.parseArray(pool.getScopeId(), String.class);
                    return CollectionUtils.isNotEmpty(poolScopes) && CollectionUtils.containsAny(scopeIds, poolScopes);
                })
                .sorted(Comparator.comparing(CustomerPool::getCreateTime).reversed())
                .toList();
    }

    /**
     * 计算剩余归属天数
     *
     * @param pool           公海池
     * @param collectionTime 领取时间
     * @param createTime     创建时间
     *
     * @return 剩余归属天数
     */
    public Integer calcReservedDay(CustomerPool pool, CustomerPoolRecycleRule recycleRule, Long collectionTime, Long createTime) {
        if (pool == null || !pool.getAuto() || recycleRule == null) {
            return null;
        }

        // 判断公海是否存在入库条件
        List<RuleConditionDTO> conditions = JSON.parseArray(recycleRule.getCondition(), RuleConditionDTO.class);
        return RecycleConditionUtils.calcRecycleDays(conditions, Math.min(collectionTime, createTime));
    }

    /**
     * 获取负责人最佳匹配公海
     *
     * @param pools 公海列表
     *
     * @return 公海集合
     */
    public Map<List<String>, CustomerPool> getOwnersBestMatchPoolMap(List<CustomerPool> pools) {
        var poolMap = new HashMap<List<String>, CustomerPool>(4);
        pools.sort(Comparator.comparing(CustomerPool::getCreateTime).reversed());
        for (CustomerPool pool : pools) {
            List<String> exitOwnerIds = poolMap.keySet().stream().flatMap(List::stream).toList();
            List<String> scopeIds = JSON.parseArray(pool.getScopeId(), String.class);
            List<String> ownerIds = userExtendService.getScopeOwnerIds(scopeIds, pool.getOrganizationId());
            List<String> defaultOwnerIds = ownerIds.stream().distinct().filter(ownerId -> !exitOwnerIds.contains(ownerId)).toList();
            if (CollectionUtils.isEmpty(defaultOwnerIds)) {
                continue;
            }
            poolMap.put(defaultOwnerIds, pool);
        }
        return poolMap;
    }

    /**
     * 判断客户是否需要回收
     *
     * @return 是否回收
     */
    public boolean checkRecycled(Customer customer, CustomerPoolRecycleRule recycleRule) {
        boolean allMatch = Strings.CS.equals(CombineSearch.SearchMode.AND.name(), recycleRule.getOperator());
        List<RuleConditionDTO> conditions = JSON.parseArray(recycleRule.getCondition(), RuleConditionDTO.class);
        if (allMatch) {
            return conditions.stream().allMatch(condition -> matchTime(condition, customer));
        } else {
            return conditions.stream().anyMatch(condition -> matchTime(condition, customer));
        }
    }

    /**
     * 是否匹配时间规则
     *
     * @param condition 规则
     * @param customer  客户
     *
     * @return 是否匹配
     */
    private boolean matchTime(RuleConditionDTO condition, Customer customer) {
        if (Strings.CS.equals(condition.getColumn(), RecycleConditionColumnKey.STORAGE_TIME)) {
            if (condition.getScope().contains(RecycleConditionScopeKey.CREATED)) {
                return RecycleConditionUtils.matchTime(condition, customer.getCreateTime());
            } else if (condition.getScope().contains(RecycleConditionScopeKey.PICKED)) {
                return RecycleConditionUtils.matchTime(condition, customer.getCollectionTime());
            } else {
                return RecycleConditionUtils.matchTime(condition, customer.getCreateTime()) || RecycleConditionUtils.matchTime(condition, customer.getCollectionTime());
            }
        } else {
            return RecycleConditionUtils.matchTime(condition, customer.getFollowTime());
        }
    }
}
