package com.njcn.gather.systemmonitor.disk.component;

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.njcn.gather.systemmonitor.disk.constant.DiskMonitorConstant;
import com.njcn.gather.systemmonitor.disk.mapper.DiskMonitorNotifyLogMapper;
import com.njcn.gather.systemmonitor.disk.pojo.dto.DiskMonitorNotifyHttpItem;
import com.njcn.gather.systemmonitor.disk.pojo.dto.DiskMonitorNotifyPathItem;
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorJob;
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorNotifyLog;
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorResult;
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorTarget;
import com.njcn.gather.systemmonitor.disk.util.GeneratedFileNameUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.OutputStream;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * 磁盘监控通知发送组件。
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class DiskMonitorNotificationComponent {

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final TypeReference<List<DiskMonitorNotifyPathItem>> PATH_LIST_TYPE = new TypeReference<List<DiskMonitorNotifyPathItem>>() {
    };
    private static final TypeReference<List<DiskMonitorNotifyHttpItem>> HTTP_LIST_TYPE = new TypeReference<List<DiskMonitorNotifyHttpItem>>() {
    };

    private final DiskMonitorNotifyLogMapper notifyLogMapper;
    private final ObjectMapper objectMapper;

    public void sendNotifications(DiskMonitorJob job, DiskMonitorTarget target, DiskMonitorResult result,
                                  BigDecimal usedPercent, String currentStatus, String notifyReason,
                                  String notifyLevel, LocalDateTime scanTime, String message) {
        List<DiskMonitorNotifyPathItem> pathItems;
        List<DiskMonitorNotifyHttpItem> httpItems;
        try {
            pathItems = parsePathItems(target.getNotifyPathListJson());
            httpItems = parseHttpItems(target.getNotifyHttpListJson());
        } catch (Exception exception) {
            insertFailedNotifyLog(job, result, target, notifyLevel, "N/A", "通知配置解析失败: " + exception.getMessage());
            return;
        }

        boolean pathConfigured = isEnabled(target.getNotifyPathEnabled()) && pathItems.stream().anyMatch(item -> Boolean.TRUE.equals(item.getEnabled()));
        boolean httpConfigured = isEnabled(target.getNotifyHttpEnabled()) && httpItems.stream().anyMatch(item -> Boolean.TRUE.equals(item.getEnabled()));
        if (!pathConfigured && !httpConfigured) {
            insertFailedNotifyLog(job, result, target, notifyLevel, "N/A", "当前盘符未配置可用通知通道");
            return;
        }

        String notifyTitle = buildNotifyTitle(target.getDriveLetter(), notifyLevel, usedPercent);
        String notifyContent = buildNotifyContent(job, target, usedPercent, currentStatus, notifyLevel, scanTime, message);

        if (pathConfigured) {
            for (DiskMonitorNotifyPathItem item : pathItems) {
                if (!Boolean.TRUE.equals(item.getEnabled())) {
                    continue;
                }
                sendPathNotification(job, target, result, notifyReason, notifyLevel, notifyTitle, notifyContent, item,
                        usedPercent, currentStatus, scanTime, message);
            }
        }
        if (httpConfigured) {
            for (DiskMonitorNotifyHttpItem item : httpItems) {
                if (!Boolean.TRUE.equals(item.getEnabled())) {
                    continue;
                }
                sendHttpNotification(job, target, result, notifyReason, notifyLevel, notifyTitle, notifyContent, item,
                        usedPercent, currentStatus, scanTime, message);
            }
        }
    }

    private void sendPathNotification(DiskMonitorJob job, DiskMonitorTarget target, DiskMonitorResult result,
                                      String notifyReason, String notifyLevel, String notifyTitle, String notifyContent,
                                      DiskMonitorNotifyPathItem item, BigDecimal usedPercent, String currentStatus,
                                      LocalDateTime scanTime, String message) {
        DiskMonitorNotifyLog notifyLog = createNotifyLog(job, result, target, notifyLevel, notifyTitle, notifyContent,
                DiskMonitorConstant.CHANNEL_TYPE_PATH, item.getPath());
        try {
            Path directoryPath = Paths.get(item.getPath());
            Files.createDirectories(directoryPath);
            String fileName = String.format("disk-monitor-%s-%s-%s.json",
                    job.getJobNo(), target.getDriveLetter().replace(":", ""),
                    LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
            Path filePath = directoryPath.resolve(GeneratedFileNameUtil.appendToday(fileName));
            Map<String, Object> payload = buildNotifyPayload(job, target, usedPercent, currentStatus, notifyReason, notifyLevel, scanTime, message);
            Files.write(filePath, objectMapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(payload));
            notifyLog.setSendStatus(DiskMonitorConstant.SEND_STATUS_SUCCESS);
            notifyLog.setResponseMessage("写入成功:" + filePath.toString());
        } catch (Exception exception) {
            log.error("磁盘监控路径通知发送失败，driveLetter={}, path={}", target.getDriveLetter(), item.getPath(), exception);
            notifyLog.setSendStatus(DiskMonitorConstant.SEND_STATUS_FAILED);
            notifyLog.setResponseMessage(exception.getMessage());
        }
        notifyLogMapper.insert(notifyLog);
    }

    private void sendHttpNotification(DiskMonitorJob job, DiskMonitorTarget target, DiskMonitorResult result,
                                      String notifyReason, String notifyLevel, String notifyTitle, String notifyContent,
                                      DiskMonitorNotifyHttpItem item, BigDecimal usedPercent, String currentStatus,
                                      LocalDateTime scanTime, String message) {
        DiskMonitorNotifyLog notifyLog = createNotifyLog(job, result, target, notifyLevel, notifyTitle, notifyContent,
                DiskMonitorConstant.CHANNEL_TYPE_HTTP, item.getUrl());
        HttpURLConnection connection = null;
        try {
            Map<String, Object> payload = buildNotifyPayload(job, target, usedPercent, currentStatus, notifyReason, notifyLevel, scanTime, message);
            byte[] body = objectMapper.writeValueAsBytes(payload);
            URL url = new URL(item.getUrl());
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod(StrUtil.blankToDefault(item.getMethod(), DiskMonitorConstant.HTTP_METHOD_POST).trim().toUpperCase(Locale.ROOT));
            connection.setConnectTimeout(resolveTimeout(item.getTimeoutMs()));
            connection.setReadTimeout(resolveTimeout(item.getTimeoutMs()));
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            connection.setDoInput(true);
            if (!"GET".equalsIgnoreCase(connection.getRequestMethod())) {
                connection.setDoOutput(true);
                try (OutputStream outputStream = connection.getOutputStream()) {
                    outputStream.write(body);
                }
            }
            int statusCode = connection.getResponseCode();
            notifyLog.setSendStatus(statusCode >= 200 && statusCode < 300
                    ? DiskMonitorConstant.SEND_STATUS_SUCCESS
                    : DiskMonitorConstant.SEND_STATUS_FAILED);
            notifyLog.setResponseMessage(statusCode + " " + connection.getResponseMessage());
        } catch (Exception exception) {
            log.error("磁盘监控HTTP通知发送失败，driveLetter={}, url={}", target.getDriveLetter(), item.getUrl(), exception);
            notifyLog.setSendStatus(DiskMonitorConstant.SEND_STATUS_FAILED);
            notifyLog.setResponseMessage(exception.getMessage());
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
        notifyLogMapper.insert(notifyLog);
    }

    private Map<String, Object> buildNotifyPayload(DiskMonitorJob job, DiskMonitorTarget target, BigDecimal usedPercent,
                                                   String currentStatus, String notifyReason, String notifyLevel,
                                                   LocalDateTime scanTime, String message) {
        Map<String, Object> payload = new LinkedHashMap<>();
        payload.put("jobNo", job.getJobNo());
        payload.put("driveLetter", target.getDriveLetter());
        payload.put("currentStatus", currentStatus);
        payload.put("notifyReason", notifyReason);
        payload.put("notifyLevel", notifyLevel);
        payload.put("usedPercent", usedPercent);
        payload.put("warningUsagePercent", target.getWarningUsagePercent());
        payload.put("alarmUsagePercent", target.getAlarmUsagePercent());
        payload.put("scanTime", formatDateTime(scanTime));
        payload.put("message", message);
        return payload;
    }

    private String buildNotifyTitle(String driveLetter, String notifyLevel, BigDecimal usedPercent) {
        return String.format("磁盘监控通知[%s] %s 使用率%s%%",
                notifyLevel, driveLetter, usedPercent == null ? "0.00" : usedPercent.toPlainString());
    }

    private String buildNotifyContent(DiskMonitorJob job, DiskMonitorTarget target, BigDecimal usedPercent,
                                      String currentStatus, String notifyLevel, LocalDateTime scanTime, String message) {
        StringBuilder builder = new StringBuilder();
        builder.append("任务编号：").append(job.getJobNo())
                .append("；盘符：").append(target.getDriveLetter())
                .append("；当前状态：").append(currentStatus)
                .append("；通知级别：").append(notifyLevel)
                .append("；使用率：").append(usedPercent == null ? "0.00" : usedPercent.toPlainString()).append("%")
                .append("；预警阈值：").append(target.getWarningUsagePercent()).append("%")
                .append("；告警阈值：").append(target.getAlarmUsagePercent()).append("%")
                .append("；扫描时间：").append(formatDateTime(scanTime));
        if (StrUtil.isNotBlank(message)) {
            builder.append("；说明：").append(message);
        }
        return builder.toString();
    }

    private DiskMonitorNotifyLog createNotifyLog(DiskMonitorJob job, DiskMonitorResult result, DiskMonitorTarget target,
                                                 String notifyLevel, String notifyTitle, String notifyContent,
                                                 String channelType, String channelTarget) {
        DiskMonitorNotifyLog notifyLog = new DiskMonitorNotifyLog();
        notifyLog.setJobId(job.getId());
        notifyLog.setResultId(result.getId());
        notifyLog.setTargetId(target.getId());
        notifyLog.setDriveLetter(target.getDriveLetter());
        notifyLog.setNotifyLevel(notifyLevel);
        notifyLog.setChannelType(channelType);
        notifyLog.setChannelTarget(channelTarget);
        notifyLog.setNotifyTitle(notifyTitle);
        notifyLog.setNotifyContent(notifyContent);
        notifyLog.setSentAt(LocalDateTime.now());
        return notifyLog;
    }

    private void insertFailedNotifyLog(DiskMonitorJob job, DiskMonitorResult result, DiskMonitorTarget target,
                                       String notifyLevel, String channelTarget, String responseMessage) {
        DiskMonitorNotifyLog notifyLog = createNotifyLog(job, result, target, notifyLevel,
                "磁盘监控通知失败", responseMessage, DiskMonitorConstant.CHANNEL_TYPE_HTTP, channelTarget);
        notifyLog.setSendStatus(DiskMonitorConstant.SEND_STATUS_FAILED);
        notifyLog.setResponseMessage(responseMessage);
        notifyLogMapper.insert(notifyLog);
    }

    private List<DiskMonitorNotifyPathItem> parsePathItems(String json) throws Exception {
        return readJsonList(json, PATH_LIST_TYPE);
    }

    private List<DiskMonitorNotifyHttpItem> parseHttpItems(String json) throws Exception {
        return readJsonList(json, HTTP_LIST_TYPE);
    }

    private <T> List<T> readJsonList(String json, TypeReference<List<T>> typeReference) throws Exception {
        if (StrUtil.isBlank(json)) {
            return new ArrayList<>();
        }
        List<T> result = objectMapper.readValue(json, typeReference);
        return result == null ? new ArrayList<>() : result;
    }

    private boolean isEnabled(Integer value) {
        return value != null && value == 1;
    }

    private int resolveTimeout(Integer timeoutMs) {
        return timeoutMs == null || timeoutMs <= 0 ? DiskMonitorConstant.DEFAULT_HTTP_TIMEOUT_MS : timeoutMs;
    }

    private String formatDateTime(LocalDateTime dateTime) {
        return dateTime == null ? null : dateTime.format(DATE_TIME_FORMATTER);
    }
}
