package com.qqchen.deploy.backend.schedule.job;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.schedule.dto.JobStatusDTO;
import com.qqchen.deploy.backend.schedule.entity.ScheduleJob;
import com.qqchen.deploy.backend.schedule.entity.ScheduleJobLog;
import com.qqchen.deploy.backend.schedule.enums.JobStatusEnum;
import com.qqchen.deploy.backend.schedule.repository.IScheduleJobLogRepository;
import com.qqchen.deploy.backend.schedule.repository.IScheduleJobRepository;
import com.qqchen.deploy.backend.schedule.service.JobStatusRedisService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.context.ApplicationContext;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 智能双模式任务调度器
 * 
 * 支持两种执行模式：
 * 1. IScheduleJob接口模式：适用于复杂任务（有进度报告、多步骤等）
 * 2. Service方法调用模式：适用于简单任务（直接调用Service方法，纯配置化）
 * 
 * 使用示例：
 * 
 * 【模式1 - 复杂任务】
 * 配置: beanName="workflowCleanJob", methodName="execute", methodParams="{...}"
 * 要求: WorkflowCleanJob 需要继承 AbstractScheduleJob
 * 
 * 【模式2 - 简单任务】
 * 配置: beanName="repositoryGroupService", methodName="syncGroups", methodParams='{"externalSystemId":1}'
 * 要求: 方法必须存在，参数会自动解析和匹配
 *
 * @author qichen
 */
@Slf4j
public class DynamicJob implements Job {

    @Resource
    private ApplicationContext applicationContext;
    
    @Resource
    private ObjectMapper objectMapper;
    
    @Resource
    private JobStatusRedisService jobStatusRedisService;
    
    @Resource
    private IScheduleJobLogRepository jobLogRepository;
    
    @Resource
    private IScheduleJobRepository jobRepository;
    
    @Resource
    private Scheduler scheduler;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 从JobDataMap中获取参数
        String beanName = context.getMergedJobDataMap().getString("beanName");
        String methodName = context.getMergedJobDataMap().getString("methodName");
        String methodParams = context.getMergedJobDataMap().getString("methodParams");
        Long jobId = context.getMergedJobDataMap().getLong("jobId");
        String jobName = context.getMergedJobDataMap().getString("jobName");
        LocalDateTime startTime = LocalDateTime.now();

        log.info("开始执行定时任务：jobId={}, jobName={}, beanName={}, methodName={}", 
            jobId, jobName, beanName, methodName);

        try {
            // 1. 记录开始状态到Redis
            saveStartStatus(jobId, jobName, startTime);
            
            // 2. 获取Bean实例
            Object bean = applicationContext.getBean(beanName);
            if (bean == null) {
                throw new JobExecutionException("找不到Bean：" + beanName);
            }

            // 3. 执行业务逻辑（智能识别模式）
            if (bean instanceof IScheduleJob) {
                executeScheduleJob((IScheduleJob) bean, methodParams);
            } else {
                executeServiceMethod(bean, methodName, methodParams);
            }

            // 4. 记录成功状态和日志
            log.info("定时任务执行成功：jobId={}, jobName={}", jobId, jobName);
            saveSuccessStatus(jobId, jobName, startTime);
            saveSuccessLog(jobId, jobName, beanName, methodName, startTime);
            updateJobStatistics(jobId, true);

        } catch (Exception e) {
            // 5. 记录失败状态和日志
            log.error("定时任务执行失败：jobId={}, jobName={}", jobId, jobName, e);
            saveFailStatus(jobId, jobName, startTime, e);
            saveFailLog(jobId, jobName, beanName, methodName, startTime, e);
            updateJobStatistics(jobId, false);
            throw new JobExecutionException(e);
        }
    }

    /**
     * 执行IScheduleJob接口任务（模式1：复杂任务）
     */
    private void executeScheduleJob(IScheduleJob scheduleJob, String methodParams) throws Exception {
        Map<String, Object> params = parseParamsToMap(methodParams);
        scheduleJob.execute(params);
    }

    /**
     * 执行普通Service方法（模式2：简单任务，配置化调用）
     */
    private void executeServiceMethod(Object bean, String methodName, String methodParams) throws Exception {
        // 解析参数
        Map<String, Object> paramsMap = parseParamsToMap(methodParams);
        
        // 查找匹配的方法
        Method method = findBestMatchMethod(bean.getClass(), methodName, paramsMap);
        if (method == null) {
            throw new NoSuchMethodException(
                String.format("找不到方法：%s.%s，参数：%s", 
                    bean.getClass().getSimpleName(), methodName, paramsMap.keySet()));
        }

        // 准备方法参数
        Object[] args = prepareMethodArgs(method, paramsMap);
        
        // 调用方法
        log.debug("调用方法：{}.{}，参数：{}", bean.getClass().getSimpleName(), methodName, args);
        method.invoke(bean, args);
    }

    /**
     * 查找最佳匹配的方法
     * 支持参数名匹配（推荐）和参数数量匹配（兼容）
     */
    private Method findBestMatchMethod(Class<?> clazz, String methodName, Map<String, Object> paramsMap) {
        Method[] methods = clazz.getMethods();
        Method bestMatch = null;
        int bestScore = -1;

        for (Method method : methods) {
            if (!method.getName().equals(methodName)) {
                continue;
            }

            int paramCount = method.getParameterCount();
            
            // 无参方法
            if (paramCount == 0 && paramsMap.isEmpty()) {
                return method;
            }

            // 参数数量匹配检查
            if (paramCount == paramsMap.size()) {
                // 计算匹配得分（参数类型兼容性）
                int score = calculateMatchScore(method, paramsMap);
                if (score > bestScore) {
                    bestScore = score;
                    bestMatch = method;
                }
            }
        }

        return bestMatch;
    }

    /**
     * 计算方法参数匹配得分
     */
    private int calculateMatchScore(Method method, Map<String, Object> paramsMap) {
        Class<?>[] paramTypes = method.getParameterTypes();
        List<Object> paramValues = new ArrayList<>(paramsMap.values());
        
        int score = 0;
        for (int i = 0; i < paramTypes.length; i++) {
            if (i >= paramValues.size()) {
                return -1; // 参数不足
            }
            
            Object value = paramValues.get(i);
            Class<?> targetType = paramTypes[i];
            
            if (value == null) {
                // null可以赋值给任何引用类型
                if (!targetType.isPrimitive()) {
                    score += 50;
                } else {
                    return -1; // null不能赋值给基本类型
                }
            } else if (isTypeCompatible(targetType, value.getClass())) {
                score += 100; // 完全兼容
            } else if (isNumberConvertible(targetType, value)) {
                score += 80; // 数字类型可转换
            } else {
                return -1; // 类型不兼容
            }
        }
        
        return score;
    }

    /**
     * 准备方法参数数组
     */
    private Object[] prepareMethodArgs(Method method, Map<String, Object> paramsMap) {
        Class<?>[] paramTypes = method.getParameterTypes();
        List<Object> paramValues = new ArrayList<>(paramsMap.values());
        Object[] args = new Object[paramTypes.length];

        for (int i = 0; i < paramTypes.length; i++) {
            Object value = i < paramValues.size() ? paramValues.get(i) : null;
            args[i] = convertParameter(value, paramTypes[i]);
        }

        return args;
    }

    /**
     * 转换参数类型
     */
    private Object convertParameter(Object value, Class<?> targetType) {
        if (value == null) {
            return null;
        }

        // 已经是目标类型
        if (targetType.isInstance(value)) {
            return value;
        }

        // 数字类型转换
        if (value instanceof Number) {
            Number num = (Number) value;
            if (targetType == Long.class || targetType == long.class) {
                return num.longValue();
            } else if (targetType == Integer.class || targetType == int.class) {
                return num.intValue();
            } else if (targetType == Double.class || targetType == double.class) {
                return num.doubleValue();
            } else if (targetType == Float.class || targetType == float.class) {
                return num.floatValue();
            }
        }

        return value;
    }

    /**
     * 检查类型是否兼容
     */
    private boolean isTypeCompatible(Class<?> target, Class<?> source) {
        if (target.isAssignableFrom(source)) {
            return true;
        }

        // 基本类型和包装类型互转
        if (target == int.class && source == Integer.class) return true;
        if (target == Integer.class && source == int.class) return true;
        if (target == long.class && source == Long.class) return true;
        if (target == Long.class && source == long.class) return true;
        if (target == double.class && source == Double.class) return true;
        if (target == Double.class && source == double.class) return true;
        if (target == boolean.class && source == Boolean.class) return true;
        if (target == Boolean.class && source == boolean.class) return true;

        return false;
    }

    /**
     * 检查数字是否可转换
     */
    private boolean isNumberConvertible(Class<?> target, Object value) {
        if (!(value instanceof Number)) {
            return false;
        }
        return target == Long.class || target == long.class ||
               target == Integer.class || target == int.class ||
               target == Double.class || target == double.class ||
               target == Float.class || target == float.class;
    }

    /**
     * 解析参数为Map格式
     */
    private Map<String, Object> parseParamsToMap(String methodParams) {
        if (methodParams == null || methodParams.trim().isEmpty()) {
            return Map.of();
        }

        try {
            return objectMapper.readValue(methodParams, Map.class);
        } catch (Exception e) {
            log.warn("解析参数失败，返回空Map：{}", methodParams, e);
            return Map.of();
        }
    }

    // ======================== 任务监控相关方法 ========================

    /**
     * 保存任务开始状态到Redis
     */
    private void saveStartStatus(Long jobId, String jobName, LocalDateTime startTime) {
        try {
            JobStatusDTO statusDTO = JobStatusDTO.builder()
                    .jobId(jobId)
                    .jobName(jobName)
                    .status("RUNNING")
                    .progress(0)
                    .currentStep("任务执行中")
                    .message("任务执行中")
                    .startTime(startTime)
                    .build();
            jobStatusRedisService.saveJobStatus(jobId, statusDTO);
        } catch (Exception e) {
            log.warn("保存任务开始状态失败：jobId={}", jobId, e);
        }
    }

    /**
     * 保存任务成功状态到Redis
     */
    private void saveSuccessStatus(Long jobId, String jobName, LocalDateTime startTime) {
        try {
            JobStatusDTO statusDTO = JobStatusDTO.builder()
                    .jobId(jobId)
                    .jobName(jobName)
                    .status("SUCCESS")
                    .progress(100)
                    .currentStep("任务执行成功")
                    .message("任务执行成功")
                    .startTime(startTime)
                    .build();
            jobStatusRedisService.saveJobStatus(jobId, statusDTO);
        } catch (Exception e) {
            log.warn("保存任务成功状态失败：jobId={}", jobId, e);
        }
    }

    /**
     * 保存任务失败状态到Redis
     */
    private void saveFailStatus(Long jobId, String jobName, LocalDateTime startTime, Exception exception) {
        try {
            JobStatusDTO statusDTO = JobStatusDTO.builder()
                    .jobId(jobId)
                    .jobName(jobName)
                    .status("FAIL")
                    .progress(null)
                    .currentStep("任务执行失败")
                    .message("任务执行失败: " + exception.getMessage())
                    .startTime(startTime)
                    .build();
            jobStatusRedisService.saveJobStatus(jobId, statusDTO);
        } catch (Exception e) {
            log.warn("保存任务失败状态失败：jobId={}", jobId, e);
        }
    }

    /**
     * 保存任务成功日志到数据库
     */
    private void saveSuccessLog(Long jobId, String jobName, String beanName, String methodName, LocalDateTime startTime) {
        try {
            ScheduleJobLog jobLog = new ScheduleJobLog();
            jobLog.setJobId(jobId);
            jobLog.setJobName(jobName);
            jobLog.setBeanName(beanName);
            jobLog.setMethodName(methodName);
            jobLog.setExecuteTime(startTime);
            jobLog.setFinishTime(LocalDateTime.now());
            jobLog.setDuration(Duration.between(startTime, LocalDateTime.now()).toMillis());
            jobLog.setStatus(JobStatusEnum.SUCCESS);
            jobLog.setResultMessage("任务执行成功");
            setServerInfo(jobLog);
            jobLogRepository.save(jobLog);
        } catch (Exception e) {
            log.error("保存任务成功日志失败：jobId={}", jobId, e);
        }
    }

    /**
     * 保存任务失败日志到数据库
     */
    private void saveFailLog(Long jobId, String jobName, String beanName, String methodName, 
                             LocalDateTime startTime, Exception exception) {
        try {
            ScheduleJobLog jobLog = new ScheduleJobLog();
            jobLog.setJobId(jobId);
            jobLog.setJobName(jobName);
            jobLog.setBeanName(beanName);
            jobLog.setMethodName(methodName);
            jobLog.setExecuteTime(startTime);
            jobLog.setFinishTime(LocalDateTime.now());
            jobLog.setDuration(Duration.between(startTime, LocalDateTime.now()).toMillis());
            jobLog.setStatus(JobStatusEnum.FAIL);
            jobLog.setResultMessage("任务执行失败");
            jobLog.setExceptionInfo(getStackTrace(exception));
            setServerInfo(jobLog);
            jobLogRepository.save(jobLog);
        } catch (Exception e) {
            log.error("保存任务失败日志失败：jobId={}", jobId, e);
        }
    }

    /**
     * 设置服务器信息
     */
    private void setServerInfo(ScheduleJobLog jobLog) {
        try {
            InetAddress addr = InetAddress.getLocalHost();
            jobLog.setServerIp(addr.getHostAddress());
            jobLog.setServerHost(addr.getHostName());
        } catch (Exception e) {
            log.warn("获取服务器信息失败", e);
        }
    }

    /**
     * 更新任务统计信息
     */
    private void updateJobStatistics(Long jobId, boolean success) {
        try {
            ScheduleJob job = jobRepository.findById(jobId).orElse(null);
            if (job == null) {
                log.warn("更新任务统计失败：任务不存在，jobId={}", jobId);
                return;
            }

            // 更新执行次数
            Integer executeCount = job.getExecuteCount();
            job.setExecuteCount(executeCount == null ? 1 : executeCount + 1);

            // 更新成功/失败次数
            if (success) {
                Integer successCount = job.getSuccessCount();
                job.setSuccessCount(successCount == null ? 1 : successCount + 1);
            } else {
                Integer failCount = job.getFailCount();
                job.setFailCount(failCount == null ? 1 : failCount + 1);
            }

            // 更新上次执行时间
            job.setLastExecuteTime(LocalDateTime.now());

            // 更新下次执行时间（从Quartz Trigger获取）
            try {
                TriggerKey triggerKey = TriggerKey.triggerKey("trigger_" + jobId, "DEFAULT");
                CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
                if (trigger != null) {
                    Date nextFireTime = trigger.getNextFireTime();
                    if (nextFireTime != null) {
                        job.setNextExecuteTime(LocalDateTime.ofInstant(
                            nextFireTime.toInstant(), 
                            ZoneId.systemDefault()
                        ));
                    }
                }
            } catch (SchedulerException e) {
                log.warn("获取下次执行时间失败：jobId={}", jobId, e);
            }

            // 保存更新
            jobRepository.save(job);
            log.info("任务统计信息已更新：jobId={}, executeCount={}, successCount={}, failCount={}", 
                jobId, job.getExecuteCount(), job.getSuccessCount(), job.getFailCount());

        } catch (Exception e) {
            log.error("更新任务统计失败：jobId={}", jobId, e);
        }
    }

    /**
     * 获取异常堆栈信息
     */
    private String getStackTrace(Throwable throwable) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        throwable.printStackTrace(pw);
        return sw.toString();
    }
}

