package com.writeoff.module.notification.service;

import com.writeoff.module.notification.model.PlatformNotifyGatewayResolvedConfig;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
import java.util.Map;

@Service
public class NotificationDeliveryProtectionService {
    private final JdbcTemplate jdbcTemplate;
    private final PlatformNotifyGatewayService gatewayService;

    public NotificationDeliveryProtectionService(JdbcTemplate jdbcTemplate,
                                                 PlatformNotifyGatewayService gatewayService) {
        this.jdbcTemplate = jdbcTemplate;
        this.gatewayService = gatewayService;
    }

    public GuardDecision checkBeforeSend(String channelCode, String receiverRef) {
        String channel = normalizeChannel(channelCode);
        if (!isProtectedChannel(channel)) {
            return GuardDecision.allow();
        }
        BreakerState breakerState = loadBreakerState(channel);
        if (breakerState.breakerUntil != null && LocalDateTime.now().isBefore(breakerState.breakerUntil)) {
            String untilText = breakerState.breakerUntil.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return GuardDecision.block("BREAKER_OPEN", "渠道熔断中，请在 " + untilText + " 后重试");
        }
        if ("SMS".equals(channel)) {
            SmsGuardConfig config = resolveSmsConfig(channel);
            GuardRecord record = loadGuardRecord(channel, normalizeReceiver(receiverRef));
            LocalDateTime now = LocalDateTime.now();
            if (record.lastSentAt != null && config.quietPeriodSeconds > 0 && now.isBefore(record.lastSentAt.plusSeconds(config.quietPeriodSeconds))) {
                return GuardDecision.block("SMS_RATE_LIMIT_WINDOW", "同一手机号发送过于频繁，请稍后再试");
            }
            if (config.dailyLimit > 0 && record.dailyCount >= config.dailyLimit) {
                return GuardDecision.block("SMS_RATE_LIMIT_DAILY", "同一手机号当日发送次数已达上限");
            }
        }
        return GuardDecision.allow();
    }

    public void recordSuccess(String channelCode, String receiverRef) {
        String channel = normalizeChannel(channelCode);
        if (!isProtectedChannel(channel)) {
            return;
        }
        if ("SMS".equals(channel)) {
            String normalizedReceiver = normalizeReceiver(receiverRef);
            jdbcTemplate.update(
                    "INSERT INTO platform_notify_delivery_guard (channel_code, receiver_ref, stat_date, daily_count, last_sent_at) " +
                            "VALUES (?, ?, CURRENT_DATE(), 1, CURRENT_TIMESTAMP) " +
                            "ON DUPLICATE KEY UPDATE daily_count=daily_count+1, last_sent_at=VALUES(last_sent_at), updated_at=CURRENT_TIMESTAMP",
                    channel,
                    normalizedReceiver
            );
        }
        jdbcTemplate.update(
                "UPDATE platform_notify_circuit_breaker SET consecutive_failures=0, breaker_until=NULL, last_failure_message=NULL, updated_at=CURRENT_TIMESTAMP WHERE channel_code=?",
                channel
        );
    }

    public void recordFailure(String channelCode, String failureMessage) {
        String channel = normalizeChannel(channelCode);
        if (!isProtectedChannel(channel)) {
            return;
        }
        BreakerState state = loadBreakerState(channel);
        int threshold = resolveFailureThreshold(channel);
        int cooldownSeconds = resolveBreakerCooldownSeconds(channel);
        int nextFailures = state.consecutiveFailures + 1;
        LocalDateTime breakerUntil = nextFailures >= threshold
                ? LocalDateTime.now().plusSeconds(Math.max(cooldownSeconds, 1))
                : null;
        if (state.exists) {
            jdbcTemplate.update(
                    "UPDATE platform_notify_circuit_breaker SET consecutive_failures=?, breaker_until=?, last_failure_message=?, updated_at=CURRENT_TIMESTAMP WHERE channel_code=?",
                    nextFailures,
                    toTimestamp(breakerUntil),
                    trimMessage(failureMessage),
                    channel
            );
            return;
        }
        jdbcTemplate.update(
                "INSERT INTO platform_notify_circuit_breaker (channel_code, consecutive_failures, breaker_until, last_failure_message) VALUES (?, ?, ?, ?)",
                channel,
                nextFailures,
                toTimestamp(breakerUntil),
                trimMessage(failureMessage)
        );
    }

    private GuardRecord loadGuardRecord(String channelCode, String receiverRef) {
        List<Map<String, Object>> rows = jdbcTemplate.queryForList(
                "SELECT daily_count, last_sent_at FROM platform_notify_delivery_guard WHERE channel_code=? AND receiver_ref=? AND stat_date=CURRENT_DATE() LIMIT 1",
                channelCode,
                receiverRef
        );
        if (rows.isEmpty()) {
            return new GuardRecord(0, null);
        }
        Map<String, Object> row = rows.get(0);
        return new GuardRecord(intValue(row.get("daily_count"), 0), toLocalDateTime(row.get("last_sent_at")));
    }

    private BreakerState loadBreakerState(String channelCode) {
        List<Map<String, Object>> rows = jdbcTemplate.queryForList(
                "SELECT consecutive_failures, breaker_until FROM platform_notify_circuit_breaker WHERE channel_code=? LIMIT 1",
                channelCode
        );
        if (rows.isEmpty()) {
            return new BreakerState(false, 0, null);
        }
        Map<String, Object> row = rows.get(0);
        return new BreakerState(true, intValue(row.get("consecutive_failures"), 0), toLocalDateTime(row.get("breaker_until")));
    }

    private SmsGuardConfig resolveSmsConfig(String channelCode) {
        PlatformNotifyGatewayResolvedConfig resolved = gatewayService.resolveChannelConfig(channelCode, false);
        Map<String, Object> config = resolved == null ? null : resolved.getConfig();
        return new SmsGuardConfig(
                intValue(config == null ? null : config.get("quietPeriodSeconds"), 30),
                intValue(config == null ? null : config.get("dailyLimit"), 10)
        );
    }

    private int resolveFailureThreshold(String channelCode) {
        PlatformNotifyGatewayResolvedConfig resolved = gatewayService.resolveChannelConfig(channelCode, false);
        Map<String, Object> config = resolved == null ? null : resolved.getConfig();
        return Math.max(intValue(config == null ? null : config.get("failureThreshold"), 3), 1);
    }

    private int resolveBreakerCooldownSeconds(String channelCode) {
        PlatformNotifyGatewayResolvedConfig resolved = gatewayService.resolveChannelConfig(channelCode, false);
        Map<String, Object> config = resolved == null ? null : resolved.getConfig();
        return Math.max(intValue(config == null ? null : config.get("breakerCooldownSeconds"), 300), 1);
    }

    private boolean isProtectedChannel(String channelCode) {
        return "EMAIL".equals(channelCode) || "SMS".equals(channelCode);
    }

    private String normalizeChannel(String channelCode) {
        return channelCode == null ? "" : channelCode.trim().toUpperCase(Locale.ROOT);
    }

    private String normalizeReceiver(String receiverRef) {
        return receiverRef == null ? "" : receiverRef.trim();
    }

    private String trimMessage(String value) {
        String text = value == null ? "" : value.trim();
        return text.length() > 500 ? text.substring(0, 500) : text;
    }

    private int intValue(Object value, int fallback) {
        if (value == null) {
            return fallback;
        }
        try {
            return value instanceof Number ? ((Number) value).intValue() : Integer.parseInt(String.valueOf(value).trim());
        } catch (Exception ex) {
            return fallback;
        }
    }

    private Timestamp toTimestamp(LocalDateTime value) {
        return value == null ? null : Timestamp.valueOf(value);
    }

    private LocalDateTime toLocalDateTime(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Timestamp) {
            return ((Timestamp) value).toLocalDateTime();
        }
        if (value instanceof java.util.Date) {
            return new Timestamp(((java.util.Date) value).getTime()).toLocalDateTime();
        }
        if (value instanceof LocalDateTime) {
            return (LocalDateTime) value;
        }
        if (value instanceof LocalDate) {
            return ((LocalDate) value).atStartOfDay();
        }
        try {
            return LocalDateTime.parse(String.valueOf(value).trim().replace(' ', 'T'));
        } catch (Exception ex) {
            return null;
        }
    }

    public static final class GuardDecision {
        private final boolean allowed;
        private final String code;
        private final String message;

        private GuardDecision(boolean allowed, String code, String message) {
            this.allowed = allowed;
            this.code = code;
            this.message = message;
        }

        public static GuardDecision allow() {
            return new GuardDecision(true, "", "");
        }

        public static GuardDecision block(String code, String message) {
            return new GuardDecision(false, code, message);
        }

        public boolean isAllowed() {
            return allowed;
        }

        public String getCode() {
            return code;
        }

        public String getMessage() {
            return message;
        }
    }

    private static final class GuardRecord {
        private final int dailyCount;
        private final LocalDateTime lastSentAt;

        private GuardRecord(int dailyCount, LocalDateTime lastSentAt) {
            this.dailyCount = dailyCount;
            this.lastSentAt = lastSentAt;
        }
    }

    private static final class BreakerState {
        private final boolean exists;
        private final int consecutiveFailures;
        private final LocalDateTime breakerUntil;

        private BreakerState(boolean exists, int consecutiveFailures, LocalDateTime breakerUntil) {
            this.exists = exists;
            this.consecutiveFailures = consecutiveFailures;
            this.breakerUntil = breakerUntil;
        }
    }

    private static final class SmsGuardConfig {
        private final int quietPeriodSeconds;
        private final int dailyLimit;

        private SmsGuardConfig(int quietPeriodSeconds, int dailyLimit) {
            this.quietPeriodSeconds = quietPeriodSeconds;
            this.dailyLimit = dailyLimit;
        }
    }
}
