package com.njcn.gather.systemmonitor.disk.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.systemmonitor.disk.constant.DiskMonitorConstant;
import com.njcn.gather.systemmonitor.disk.enums.DiskMonitorResponseEnum;
import com.njcn.gather.systemmonitor.disk.event.DiskMonitorPolicyChangedEvent;
import com.njcn.gather.systemmonitor.disk.mapper.DiskMonitorPolicyMapper;
import com.njcn.gather.systemmonitor.disk.mapper.DiskMonitorTargetMapper;
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.param.DiskMonitorParam;
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorPolicy;
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorTarget;
import com.njcn.gather.systemmonitor.disk.pojo.vo.DiskMonitorVO;
import com.njcn.gather.systemmonitor.disk.service.IDiskMonitorPolicyService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.net.URL;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 磁盘监控配置服务实现。
 */
@Service
@RequiredArgsConstructor
public class DiskMonitorPolicyServiceImpl implements IDiskMonitorPolicyService {

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("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 DiskMonitorPolicyMapper policyMapper;
    private final DiskMonitorTargetMapper targetMapper;
    private final ObjectMapper objectMapper;
    private final ApplicationEventPublisher applicationEventPublisher;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public DiskMonitorVO.PolicyDetailVO getPolicyDetail() {
        DiskMonitorPolicy policy = getOrCreatePolicy();
        DiskMonitorVO.PolicyDetailVO detailVO = new DiskMonitorVO.PolicyDetailVO();
        detailVO.setPolicy(toPolicyVO(policy));
        detailVO.setTargets(listTargetsByPolicyId(policy.getId()).stream().map(this::toTargetVO).collect(Collectors.toList()));
        return detailVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean savePolicy(DiskMonitorParam.PolicySaveParam param) {
        validateSaveParam(param);
        DiskMonitorPolicy currentPolicy = getOrCreatePolicy();
        DiskMonitorParam.PolicyParam policyParam = param.getPolicy();

        currentPolicy.setPolicyName(policyParam.getPolicyName().trim());
        currentPolicy.setMonitorEnabled(boolToInt(policyParam.getMonitorEnabled(), true));
        currentPolicy.setRunOnAppStart(boolToInt(policyParam.getRunOnAppStart(), true));
        currentPolicy.setDailyRunTime(parseTime(policyParam.getDailyRunTime()));
        currentPolicy.setWarningNotifyMode(resolveWarningNotifyMode(policyParam.getWarningNotifyMode()));
        currentPolicy.setAlarmNotifyMode(resolveAlarmNotifyMode(policyParam.getAlarmNotifyMode()));
        currentPolicy.setRemark(policyParam.getRemark());
        if (policyMapper.updateById(currentPolicy) <= 0) {
            throw new BusinessException(DiskMonitorResponseEnum.POLICY_SAVE_FAILED);
        }

        List<DiskMonitorTarget> existingTargets = listTargetsByPolicyId(currentPolicy.getId());
        Map<Long, DiskMonitorTarget> existingById = existingTargets.stream()
                .collect(Collectors.toMap(DiskMonitorTarget::getId, item -> item, (left, right) -> left, LinkedHashMap::new));
        Map<String, DiskMonitorTarget> existingByDrive = existingTargets.stream()
                .collect(Collectors.toMap(item -> normalizeDriveLetter(item.getDriveLetter()), item -> item, (left, right) -> left, LinkedHashMap::new));

        List<Long> keepIds = new ArrayList<>();
        for (DiskMonitorParam.TargetParam targetParam : param.getTargets()) {
            String driveLetter = normalizeDriveLetter(targetParam.getDriveLetter());
            DiskMonitorTarget target = targetParam.getId() == null ? null : existingById.get(targetParam.getId());
            if (target == null) {
                target = existingByDrive.get(driveLetter);
            }
            if (target == null) {
                target = new DiskMonitorTarget();
                target.setPolicyId(currentPolicy.getId());
                target.setDriveLetter(driveLetter);
                target.setLastStatus(DiskMonitorConstant.STATUS_UNKNOWN);
            }
            fillTarget(target, targetParam, driveLetter);
            if (target.getId() == null) {
                targetMapper.insert(target);
            } else {
                targetMapper.updateById(target);
            }
            keepIds.add(target.getId());
        }

        List<Long> deleteIds = existingTargets.stream()
                .map(DiskMonitorTarget::getId)
                .filter(id -> !keepIds.contains(id))
                .collect(Collectors.toList());
        if (!deleteIds.isEmpty()) {
            targetMapper.deleteBatchIds(deleteIds);
        }

        applicationEventPublisher.publishEvent(new DiskMonitorPolicyChangedEvent());
        return true;
    }

    @Override
    public DiskMonitorPolicy getCurrentPolicy() {
        return policyMapper.selectOne(new QueryWrapper<DiskMonitorPolicy>().orderByAsc("id").last("LIMIT 1"));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public DiskMonitorPolicy getOrCreatePolicy() {
        DiskMonitorPolicy policy = getCurrentPolicy();
        if (policy != null) {
            return policy;
        }
        policy = new DiskMonitorPolicy();
        policy.setPolicyName(DiskMonitorConstant.DEFAULT_POLICY_NAME);
        policy.setMonitorEnabled(1);
        policy.setRunOnAppStart(1);
        policy.setDailyRunTime(parseTime(DiskMonitorConstant.DEFAULT_DAILY_RUN_TIME));
        policy.setWarningNotifyMode(DiskMonitorConstant.WARNING_NOTIFY_MODE_STATUS_CHANGE);
        policy.setAlarmNotifyMode(DiskMonitorConstant.ALARM_NOTIFY_MODE_EVERY_TIME);
        policyMapper.insert(policy);
        return policy;
    }

    @Override
    public List<DiskMonitorTarget> listEnabledTargets() {
        return targetMapper.selectList(new QueryWrapper<DiskMonitorTarget>()
                .eq("monitor_enabled", 1)
                .orderByAsc("drive_letter", "id"));
    }

    @Override
    public DiskMonitorTarget getTargetByDriveLetter(String driveLetter) {
        return targetMapper.selectOne(new QueryWrapper<DiskMonitorTarget>()
                .eq("drive_letter", normalizeDriveLetter(driveLetter))
                .last("LIMIT 1"));
    }

    private List<DiskMonitorTarget> listTargetsByPolicyId(Long policyId) {
        return targetMapper.selectList(new QueryWrapper<DiskMonitorTarget>()
                .eq("policy_id", policyId)
                .orderByAsc("drive_letter", "id"));
    }

    private void fillTarget(DiskMonitorTarget target, DiskMonitorParam.TargetParam targetParam, String driveLetter) {
        target.setDriveLetter(driveLetter);
        target.setMonitorEnabled(boolToInt(targetParam.getMonitorEnabled(), true));
        target.setWarningUsagePercent(targetParam.getWarningUsagePercent());
        target.setAlarmUsagePercent(targetParam.getAlarmUsagePercent());
        target.setNotifyPathEnabled(boolToInt(targetParam.getNotifyPathEnabled(), false));
        target.setNotifyPathListJson(writeValueAsJson(targetParam.getNotifyPathList()));
        target.setNotifyHttpEnabled(boolToInt(targetParam.getNotifyHttpEnabled(), false));
        target.setNotifyHttpListJson(writeValueAsJson(targetParam.getNotifyHttpList()));
        target.setRemark(targetParam.getRemark());
    }

    private void validateSaveParam(DiskMonitorParam.PolicySaveParam param) {
        DiskMonitorParam.PolicyParam policyParam = param.getPolicy();
        parseTime(policyParam.getDailyRunTime());
        resolveWarningNotifyMode(policyParam.getWarningNotifyMode());
        resolveAlarmNotifyMode(policyParam.getAlarmNotifyMode());

        Map<String, Boolean> driveMap = new LinkedHashMap<>();
        for (DiskMonitorParam.TargetParam targetParam : param.getTargets()) {
            String driveLetter = normalizeDriveLetter(targetParam.getDriveLetter());
            if (driveMap.put(driveLetter, Boolean.TRUE) != null) {
                throw new BusinessException(DiskMonitorResponseEnum.DRIVE_LETTER_REPEAT);
            }
            if (targetParam.getAlarmUsagePercent() < targetParam.getWarningUsagePercent()) {
                throw new BusinessException(DiskMonitorResponseEnum.USAGE_PERCENT_INVALID);
            }
            if (Boolean.TRUE.equals(targetParam.getNotifyPathEnabled())
                    && (targetParam.getNotifyPathList() == null || targetParam.getNotifyPathList().isEmpty())) {
                throw new BusinessException(DiskMonitorResponseEnum.NOTIFY_PATH_EMPTY);
            }
            if (Boolean.TRUE.equals(targetParam.getNotifyHttpEnabled())
                    && (targetParam.getNotifyHttpList() == null || targetParam.getNotifyHttpList().isEmpty())) {
                throw new BusinessException(DiskMonitorResponseEnum.NOTIFY_HTTP_EMPTY);
            }
            if (targetParam.getNotifyPathList() != null) {
                for (DiskMonitorNotifyPathItem item : targetParam.getNotifyPathList()) {
                    if (StrUtil.isBlank(item.getPath())) {
                        throw new BusinessException(DiskMonitorResponseEnum.NOTIFY_PATH_VALUE_EMPTY);
                    }
                }
            }
            if (targetParam.getNotifyHttpList() != null) {
                for (DiskMonitorNotifyHttpItem item : targetParam.getNotifyHttpList()) {
                    validateHttpUrl(item.getUrl());
                }
            }
        }
    }

    private String resolveWarningNotifyMode(String mode) {
        String value = StrUtil.blankToDefault(mode, DiskMonitorConstant.WARNING_NOTIFY_MODE_STATUS_CHANGE).trim().toUpperCase(Locale.ROOT);
        if (!DiskMonitorConstant.WARNING_NOTIFY_MODE_STATUS_CHANGE.equals(value)) {
            throw new BusinessException(DiskMonitorResponseEnum.POLICY_MODE_INVALID);
        }
        return value;
    }

    private String resolveAlarmNotifyMode(String mode) {
        String value = StrUtil.blankToDefault(mode, DiskMonitorConstant.ALARM_NOTIFY_MODE_EVERY_TIME).trim().toUpperCase(Locale.ROOT);
        if (!DiskMonitorConstant.ALARM_NOTIFY_MODE_EVERY_TIME.equals(value)) {
            throw new BusinessException(DiskMonitorResponseEnum.POLICY_MODE_INVALID);
        }
        return value;
    }

    private void validateHttpUrl(String url) {
        try {
            URL httpUrl = new URL(url);
            httpUrl.toURI();
            String protocol = httpUrl.getProtocol();
            if (!"http".equalsIgnoreCase(protocol) && !"https".equalsIgnoreCase(protocol)) {
                throw new BusinessException(DiskMonitorResponseEnum.NOTIFY_HTTP_URL_INVALID);
            }
        } catch (Exception exception) {
            throw new BusinessException(DiskMonitorResponseEnum.NOTIFY_HTTP_URL_INVALID);
        }
    }

    private String normalizeDriveLetter(String driveLetter) {
        if (StrUtil.isBlank(driveLetter)) {
            throw new BusinessException(DiskMonitorResponseEnum.DRIVE_LETTER_FORMAT_ERROR);
        }
        String normalized = driveLetter.trim().toUpperCase(Locale.ROOT);
        if (!normalized.matches("^[A-Z]:$")) {
            throw new BusinessException(DiskMonitorResponseEnum.DRIVE_LETTER_FORMAT_ERROR);
        }
        return normalized;
    }

    private LocalTime parseTime(String time) {
        try {
            return LocalTime.parse(time, TIME_FORMATTER);
        } catch (DateTimeParseException exception) {
            throw new BusinessException(DiskMonitorResponseEnum.DAILY_RUN_TIME_FORMAT_ERROR);
        }
    }

    private int boolToInt(Boolean value, boolean defaultValue) {
        return Boolean.TRUE.equals(value == null ? defaultValue : value) ? 1 : 0;
    }

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

    private String writeValueAsJson(Object value) {
        if (value == null) {
            return null;
        }
        try {
            return objectMapper.writeValueAsString(value);
        } catch (JsonProcessingException exception) {
            throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage());
        }
    }

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

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

    private <T> List<T> readJsonList(String json, TypeReference<List<T>> typeReference) {
        if (StrUtil.isBlank(json)) {
            return new ArrayList<>();
        }
        try {
            List<T> result = objectMapper.readValue(json, typeReference);
            return result == null ? new ArrayList<>() : result;
        } catch (Exception exception) {
            throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage());
        }
    }

    private DiskMonitorVO.PolicyVO toPolicyVO(DiskMonitorPolicy policy) {
        DiskMonitorVO.PolicyVO policyVO = new DiskMonitorVO.PolicyVO();
        policyVO.setId(policy.getId());
        policyVO.setPolicyName(policy.getPolicyName());
        policyVO.setMonitorEnabled(isEnabled(policy.getMonitorEnabled()));
        policyVO.setRunOnAppStart(isEnabled(policy.getRunOnAppStart()));
        policyVO.setDailyRunTime(policy.getDailyRunTime() == null ? null : policy.getDailyRunTime().format(TIME_FORMATTER));
        policyVO.setWarningNotifyMode(policy.getWarningNotifyMode());
        policyVO.setAlarmNotifyMode(policy.getAlarmNotifyMode());
        policyVO.setLastJobId(policy.getLastJobId());
        policyVO.setRemark(policy.getRemark());
        return policyVO;
    }

    private DiskMonitorVO.TargetVO toTargetVO(DiskMonitorTarget target) {
        DiskMonitorVO.TargetVO targetVO = new DiskMonitorVO.TargetVO();
        targetVO.setId(target.getId());
        targetVO.setDriveLetter(target.getDriveLetter());
        targetVO.setMonitorEnabled(isEnabled(target.getMonitorEnabled()));
        targetVO.setWarningUsagePercent(target.getWarningUsagePercent());
        targetVO.setAlarmUsagePercent(target.getAlarmUsagePercent());
        targetVO.setNotifyPathEnabled(isEnabled(target.getNotifyPathEnabled()));
        targetVO.setNotifyPathList(parsePathItems(target.getNotifyPathListJson()));
        targetVO.setNotifyHttpEnabled(isEnabled(target.getNotifyHttpEnabled()));
        targetVO.setNotifyHttpList(parseHttpItems(target.getNotifyHttpListJson()));
        targetVO.setLastStatus(target.getLastStatus());
        targetVO.setLastScanTime(formatDateTime(target.getLastScanTime()));
        targetVO.setLastUsedPercent(target.getLastUsedPercent());
        targetVO.setRemark(target.getRemark());
        return targetVO;
    }

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