package com.sino.mci.send.service;

import com.sino.mci.channel.entity.ChannelAccount;
import com.sino.mci.channel.repository.ChannelAccountMapper;
import com.sino.mci.policy.engine.PolicyEngine;
import com.sino.mci.policy.engine.PolicyGroupConfig;
import com.sino.mci.policy.engine.PolicyPreviewResult;
import com.sino.mci.policy.entity.PolicyGroup;
import com.sino.mci.policy.repository.PolicyGroupMapper;
import com.sino.mci.send.dto.SendPreviewRequest;
import com.sino.mci.send.dto.SendPreviewResponse;
import com.sino.mci.send.entity.SendRecord;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

@Service
@RequiredArgsConstructor
public class SendPreviewService {

    private static final int STATUS_ENABLED = 1;

    private final SendService sendService;
    private final ChannelAccountMapper accountMapper;
    private final PolicyEngine policyEngine;
    private final PolicyGroupMapper policyGroupMapper;

    public SendPreviewResponse preview(String tenantCode, SendPreviewRequest request) {
        List<SendTarget> targets = sendService.resolveByType(tenantCode, request.getTargetType(),
                request.getTargetValue(), request.getChannelStrategy());

        int totalPersonCount = 0;
        int totalConversationCount = 0;
        int unreachableCount = 0;
        Map<String, Integer> channelBreakdown = new HashMap<>();
        Map<Long, Integer> accountIdUsage = new HashMap<>();

        for (SendTarget target : targets) {
            if (target.getPersonId() != null) {
                totalPersonCount++;
            }
            if (target.getConversationId() != null) {
                totalConversationCount++;
            }
            if (target.getAccountId() == null) {
                unreachableCount++;
            } else {
                accountIdUsage.merge(target.getAccountId(), 1, Integer::sum);
            }
            String channelType = target.getChannelType() != null ? target.getChannelType().name().toLowerCase() : "unknown";
            channelBreakdown.merge(channelType, 1, Integer::sum);
        }

        Map<String, Integer> accountUsage = buildAccountUsage(accountIdUsage);
        SendPreviewResponse.PolicyCheckInfo policyCheck = dryRunPolicyCheck(tenantCode, request, targets, accountIdUsage.keySet());

        int estimatedTimeSeconds = (int) (1 + targets.size() * 0.5 + totalConversationCount * 2);

        SendPreviewResponse response = new SendPreviewResponse();
        response.setTotalPersonCount(totalPersonCount);
        response.setTotalConversationCount(totalConversationCount);
        response.setChannelBreakdown(channelBreakdown);
        response.setUnreachableCount(unreachableCount);
        response.setAccountUsage(accountUsage);
        response.setPolicyCheck(policyCheck);
        response.setEstimatedTimeSeconds(estimatedTimeSeconds);
        return response;
    }

    private Map<String, Integer> buildAccountUsage(Map<Long, Integer> accountIdUsage) {
        Map<String, Integer> accountUsage = new HashMap<>();
        if (accountIdUsage.isEmpty()) {
            return accountUsage;
        }
        Set<Long> accountIds = accountIdUsage.keySet();
        List<ChannelAccount> accounts = accountMapper.selectBatchIds(accountIds);
        Map<Long, String> accountNames = new HashMap<>();
        for (ChannelAccount account : accounts) {
            accountNames.put(account.getId(),
                    StringUtils.hasText(account.getAccountName()) ? account.getAccountName() : String.valueOf(account.getId()));
        }
        for (Map.Entry<Long, Integer> entry : accountIdUsage.entrySet()) {
            String name = accountNames.getOrDefault(entry.getKey(), String.valueOf(entry.getKey()));
            accountUsage.put(name, entry.getValue());
        }
        return accountUsage;
    }

    private SendPreviewResponse.PolicyCheckInfo dryRunPolicyCheck(String tenantCode, SendPreviewRequest request,
                                                                  List<SendTarget> targets, Set<Long> accountIds) {
        SendPreviewResponse.PolicyCheckInfo info = new SendPreviewResponse.PolicyCheckInfo();
        info.setTimeWindowOk(true);
        info.setBlacklistHit(0);
        info.setRateLimitWarning(null);

        PolicyGroup policyGroup = loadPolicyGroup(tenantCode, extractChannelType(request.getChannelStrategy()));
        if (policyGroup == null) {
            return info;
        }

        PolicyGroupConfig config = PolicyGroupConfig.parseJson(policyGroup.getPolicies());
        if (CollectionUtils.isEmpty(config.getPolicies())) {
            return info;
        }

        LocalDateTime now = LocalDateTime.now();
        int blacklistHit = 0;
        boolean timeWindowOk = true;
        StringBuilder warning = new StringBuilder();

        Set<Long> checkedAccounts = new HashSet<>(accountIds);
        if (checkedAccounts.isEmpty()) {
            checkedAccounts.add(null);
        }
        for (Long accountId : checkedAccounts) {
            SendTarget representative = targets.stream()
                    .filter(t -> Objects.equals(t.getAccountId(), accountId))
                    .findFirst()
                    .orElse(null);
            SendRecord dummy = new SendRecord();
            dummy.setTenantCode(tenantCode);
            dummy.setAccountId(accountId);
            dummy.setPersonId(representative != null ? representative.getPersonId() : null);
            dummy.setConversationId(representative != null ? representative.getConversationId() : null);
            dummy.setChannelType(extractChannelType(request.getChannelStrategy()));

            PolicyPreviewResult result = policyEngine.previewCheck(dummy, config, now);
            if (!result.isTimeWindowOk()) {
                timeWindowOk = false;
            }
            blacklistHit += result.getBlacklistHit();
            if (result.getRateLimitWarning() != null) {
                if (warning.length() > 0) {
                    warning.append("; ");
                }
                warning.append(result.getRateLimitWarning());
            }
        }

        info.setTimeWindowOk(timeWindowOk);
        info.setBlacklistHit(blacklistHit);
        info.setRateLimitWarning(warning.length() > 0 ? warning.toString() : null);
        return info;
    }

    private PolicyGroup loadPolicyGroup(String tenantCode, String channelType) {
        com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PolicyGroup> wrapper =
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
        wrapper.eq(PolicyGroup::getTenantCode, tenantCode)
                .eq(PolicyGroup::getStatus, STATUS_ENABLED);
        if (StringUtils.hasText(channelType)) {
            wrapper.eq(PolicyGroup::getChannelType, channelType);
        } else {
            wrapper.and(w -> w.isNull(PolicyGroup::getChannelType).or().eq(PolicyGroup::getChannelType, ""));
        }
        wrapper.orderByDesc(PolicyGroup::getId).last("LIMIT 1");
        PolicyGroup group = policyGroupMapper.selectOne(wrapper);
        if (group != null) {
            return group;
        }

        group = policyGroupMapper.selectOne(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PolicyGroup>()
                        .eq(PolicyGroup::getTenantCode, tenantCode)
                        .eq(PolicyGroup::getStatus, STATUS_ENABLED)
                        .and(w -> w.isNull(PolicyGroup::getChannelType).or().eq(PolicyGroup::getChannelType, ""))
                        .orderByDesc(PolicyGroup::getId)
                        .last("LIMIT 1"));
        if (group != null) {
            return group;
        }

        return policyGroupMapper.selectOne(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PolicyGroup>()
                        .eq(PolicyGroup::getGroupCode, "default")
                        .eq(PolicyGroup::getStatus, STATUS_ENABLED)
                        .orderByDesc(PolicyGroup::getId)
                        .last("LIMIT 1"));
    }

    private String extractChannelType(Map<String, Object> channelStrategy) {
        if (channelStrategy == null) {
            return "wecom";
        }
        Object value = channelStrategy.get("channel_type");
        if (value == null) {
            value = channelStrategy.get("channelType");
        }
        return value != null ? String.valueOf(value) : "wecom";
    }
}
