package com.sino.mci.policy.engine;

import com.sino.mci.send.entity.SendRecord;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;

@Slf4j
@Service
@RequiredArgsConstructor
public class PolicyEngine {

    private static final DateTimeFormatter HOURLY_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHH");
    private static final DateTimeFormatter DAILY_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
    private static final DateTimeFormatter WEEKLY_FORMATTER = DateTimeFormatter.ofPattern("YYYYww");

    private final StringRedisTemplate redisTemplate;

    public PolicyCheckResult check(SendRecord record, PolicyGroupConfig config) {
        return check(record, config, LocalDateTime.now());
    }

    public PolicyCheckResult check(SendRecord record, PolicyGroupConfig config, LocalDateTime now) {
        if (config == null || CollectionUtils.isEmpty(config.getPolicies())) {
            return allowed(null);
        }

        PolicyCheckResult blacklistResult = checkBlacklist(record, config);
        if (blacklistResult != null) {
            return blacklistResult;
        }

        PolicyCheckResult timeWindowResult = checkTimeWindow(config, now);
        if (timeWindowResult != null) {
            return timeWindowResult;
        }

        PolicyCheckResult rateLimitResult = checkRateLimit(record, config, now);
        if (rateLimitResult != null) {
            return rateLimitResult;
        }

        return allowed(config.getRetryConfig().orElse(null));
    }

    public PolicyPreviewResult previewCheck(SendRecord record, PolicyGroupConfig config, LocalDateTime now) {
        PolicyPreviewResult result = PolicyPreviewResult.builder()
                .timeWindowOk(true)
                .blacklistHit(0)
                .build();
        if (config == null || CollectionUtils.isEmpty(config.getPolicies())) {
            return result;
        }

        if (isBlacklisted(record, config)) {
            result.setBlacklistHit(1);
        }

        PolicyCheckResult timeWindowResult = checkTimeWindow(config, now);
        result.setTimeWindowOk(timeWindowResult == null);

        result.setRateLimitWarning(checkRateLimitDryRun(record, config, now));
        return result;
    }

    private PolicyCheckResult allowed(RetryConfig retryConfig) {
        return PolicyCheckResult.builder()
                .allowed(true)
                .retryConfig(retryConfig)
                .build();
    }

    private PolicyCheckResult reject(String reasonCode, String reasonMessage) {
        return PolicyCheckResult.builder()
                .allowed(false)
                .reasonCode(reasonCode)
                .reasonMessage(reasonMessage)
                .build();
    }

    private PolicyCheckResult checkBlacklist(SendRecord record, PolicyGroupConfig config) {
        for (Policy policy : config.getPolicies()) {
            if (policy.getType() != PolicyType.BLACKLIST) {
                continue;
            }
            if (!CollectionUtils.isEmpty(policy.getPersonIds())
                    && record.getPersonId() != null
                    && policy.getPersonIds().contains(record.getPersonId())) {
                return reject("blacklist_person", "发送目标人员在黑名单中");
            }
            if (!CollectionUtils.isEmpty(policy.getConversationIds())
                    && record.getConversationId() != null
                    && policy.getConversationIds().contains(record.getConversationId())) {
                return reject("blacklist_conversation", "发送目标会话在黑名单中");
            }
        }
        return null;
    }

    private boolean isBlacklisted(SendRecord record, PolicyGroupConfig config) {
        return checkBlacklist(record, config) != null;
    }

    private PolicyCheckResult checkTimeWindow(PolicyGroupConfig config, LocalDateTime now) {
        LocalTime current = now.toLocalTime();
        for (Policy policy : config.getPolicies()) {
            if (policy.getType() != PolicyType.TIME_WINDOW || CollectionUtils.isEmpty(policy.getAllow())) {
                continue;
            }
            boolean insideAny = policy.getAllow().stream()
                    .map(this::parseTimeRange)
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .anyMatch(range -> range.contains(current));
            if (!insideAny) {
                return reject("time_window_not_allowed", "当前时间不在允许发送窗口内");
            }
        }
        return null;
    }

    private Optional<TimeRange> parseTimeRange(String range) {
        if (range == null || !range.contains("-")) {
            return Optional.empty();
        }
        String[] parts = range.split("-", 2);
        try {
            LocalTime start = LocalTime.parse(parts[0].trim());
            LocalTime end = LocalTime.parse(parts[1].trim());
            return Optional.of(new TimeRange(start, end));
        } catch (Exception e) {
            log.warn("时间窗口格式错误: {}", range);
            return Optional.empty();
        }
    }

    private PolicyCheckResult checkRateLimit(SendRecord record, PolicyGroupConfig config, LocalDateTime now) {
        for (Policy policy : config.getPolicies()) {
            if (policy.getType() != PolicyType.RATE_LIMIT) {
                continue;
            }
            Long scopeId = resolveScopeId(record, policy.getScope());
            if (scopeId == null || policy.getLimit() == null || policy.getLimit() <= 0) {
                continue;
            }

            String key = buildRateLimitKey(record.getTenantCode(), policy.getScope(), scopeId, policy.getWindow(), now);
            long ttlSeconds = resolveTtlSeconds(policy.getWindow());

            Long count = redisTemplate.opsForValue().increment(key);
            if (count != null && count == 1L) {
                redisTemplate.expire(key, Duration.ofSeconds(ttlSeconds));
            }

            if (count != null && count > policy.getLimit()) {
                return reject("rate_limit_exceeded",
                        String.format("发送频率超过限制: scope=%s, limit=%d, window=%s", policy.getScope(), policy.getLimit(), policy.getWindow()));
            }
        }
        return null;
    }

    private String checkRateLimitDryRun(SendRecord record, PolicyGroupConfig config, LocalDateTime now) {
        for (Policy policy : config.getPolicies()) {
            if (policy.getType() != PolicyType.RATE_LIMIT) {
                continue;
            }
            Long scopeId = resolveScopeId(record, policy.getScope());
            if (scopeId == null || policy.getLimit() == null || policy.getLimit() <= 0) {
                continue;
            }

            String key = buildRateLimitKey(record.getTenantCode(), policy.getScope(), scopeId, policy.getWindow(), now);
            String value = redisTemplate.opsForValue().get(key);
            long count = 0L;
            if (value != null) {
                try {
                    count = Long.parseLong(value);
                } catch (NumberFormatException e) {
                    log.warn("Redis 限流计数格式错误: key={}, value={}", key, value);
                }
            }

            if (count >= policy.getLimit()) {
                return String.format("scope=%s, id=%d 已超过频率限制: limit=%d, window=%s, current=%d",
                        policy.getScope(), scopeId, policy.getLimit(), policy.getWindow(), count);
            }
            if (count >= policy.getLimit() * 0.8) {
                return String.format("scope=%s, id=%d 接近频率上限: limit=%d, window=%s, current=%d",
                        policy.getScope(), scopeId, policy.getLimit(), policy.getWindow(), count);
            }
        }
        return null;
    }

    private Long resolveScopeId(SendRecord record, String scope) {
        return switch (scope) {
            case "account" -> record.getAccountId();
            case "person" -> record.getPersonId();
            case "conversation" -> record.getConversationId();
            default -> null;
        };
    }

    private String buildRateLimitKey(String tenantCode, String scope, Long scopeId, String window, LocalDateTime now) {
        String period = switch (window) {
            case "1h" -> now.format(HOURLY_FORMATTER);
            case "7d" -> now.format(WEEKLY_FORMATTER);
            default -> now.format(DAILY_FORMATTER);
        };
        return String.format("mci:rate:%s:%s:%s:%s", tenantCode, scope, scopeId, period);
    }

    private long resolveTtlSeconds(String window) {
        return switch (window) {
            case "1h" -> 3600L;
            case "7d" -> 7 * 86400L;
            default -> 86400L;
        };
    }

    private record TimeRange(LocalTime start, LocalTime end) {

        boolean contains(LocalTime time) {
            if (!start.isAfter(end)) {
                return !time.isBefore(start) && !time.isAfter(end);
            }
            return !time.isBefore(start) || !time.isAfter(end);
        }
    }
}
