package com.qqchen.deploy.backend.deploy.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.deploy.dto.JenkinsSyncHistoryDTO;
import com.qqchen.deploy.backend.framework.utils.JsonUtils;
import com.qqchen.deploy.backend.deploy.entity.*;
import com.qqchen.deploy.backend.deploy.dto.JenkinsBuildDTO;
import com.qqchen.deploy.backend.deploy.enums.BuildTypeEnum;
import com.qqchen.deploy.backend.deploy.enums.ExternalSystemSyncStatus;
import com.qqchen.deploy.backend.deploy.enums.JenkinsSyncType;
import com.qqchen.deploy.backend.deploy.integration.response.*;
import com.qqchen.deploy.backend.deploy.query.JenkinsBuildQuery;
import com.qqchen.deploy.backend.deploy.integration.IJenkinsServiceIntegration;
import com.qqchen.deploy.backend.deploy.repository.*;
import com.qqchen.deploy.backend.deploy.service.IJenkinsBuildService;
import com.qqchen.deploy.backend.deploy.service.IJenkinsSyncHistoryService;
import com.qqchen.deploy.backend.notification.entity.NotificationChannel;
import com.qqchen.deploy.backend.notification.entity.NotificationTemplate;
import com.qqchen.deploy.backend.notification.repository.INotificationChannelRepository;
import com.qqchen.deploy.backend.notification.repository.INotificationTemplateRepository;
import com.qqchen.deploy.backend.notification.service.INotificationService;
import com.qqchen.deploy.backend.notification.service.INotificationSendService;
import com.qqchen.deploy.backend.notification.dto.SendNotificationRequest;
import com.qqchen.deploy.backend.deploy.dto.sync.JenkinsSyncContext;
import com.qqchen.deploy.backend.deploy.lock.SyncLockManager;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, JenkinsBuildDTO, JenkinsBuildQuery, Long> implements IJenkinsBuildService {

    @Resource
    private IExternalSystemRepository externalSystemRepository;

    @Resource
    private IJenkinsViewRepository jenkinsViewRepository;

    @Resource
    private IJenkinsJobRepository jenkinsJobRepository;

    @Resource
    private IJenkinsBuildRepository jenkinsBuildRepository;

    @Resource
    private IJenkinsServiceIntegration jenkinsServiceIntegration;

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private IJenkinsSyncHistoryService jenkinsSyncHistoryService;

    @Resource
    private ITeamApplicationRepository teamApplicationRepository;

    @Resource
    private IApplicationRepository applicationRepository;

    @Resource
    private IEnvironmentRepository environmentRepository;

    @Resource
    private ITeamEnvironmentNotificationConfigRepository teamEnvironmentNotificationConfigRepository;

    @Resource
    private IJenkinsBuildNotificationRepository jenkinsBuildNotificationRepository;

    @Resource
    private INotificationChannelRepository notificationChannelRepository;

    @Resource
    private INotificationTemplateRepository notificationTemplateRepository;

    @Resource
    private INotificationService notificationService;

    @Resource(name = "jenkinsTaskExecutor")
    private AsyncTaskExecutor threadPoolTaskExecutor;

    @Resource
    private SyncLockManager syncLockManager;

    @Override
    @Async
    @Transactional
    public void syncBuilds(Long externalSystemId) {
        // 尝试获取锁（自动识别调用者：JenkinsBuildServiceImpl.syncBuilds）
        if (!syncLockManager.tryLock(externalSystemId)) {
            return;
        }

        try {
            doSyncBuilds(externalSystemId, null, null);

            // 同步完成后检查并发送构建通知
            checkBuildNotifications(externalSystemId);
        } finally {
            // 释放锁
            syncLockManager.unlock(externalSystemId);
        }
    }

    private JenkinsSyncContext createSyncContext(Long externalSystemId) {
        // 1. 查询外部系统
        ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId)
            .orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND));

        // 2. 创建同步历史
        JenkinsSyncHistoryDTO syncHistory = new JenkinsSyncHistoryDTO();
        syncHistory.setExternalSystemId(externalSystemId);
        syncHistory.setSyncType(JenkinsSyncType.BUILD);
        syncHistory.setStatus(ExternalSystemSyncStatus.RUNNING);
        jenkinsSyncHistoryService.saveOrUpdateHistory(syncHistory);

        // 3. 返回同步上下文
        return new JenkinsSyncContext(externalSystem, syncHistory);
    }

    private Integer doSync(JenkinsSyncContext context) {
        // 1. 获取所有视图
        List<JenkinsView> views = jenkinsViewRepository.findByExternalSystemId(context.getExternalSystem().getId());
        if (views.isEmpty()) {
            log.debug("No views found for external system: {}", context.getExternalSystem().getId());
            updateSyncHistorySuccess(context.getSyncHistory());
            return 0;
        }

        // 2. 并发同步每个视图
        List<CompletableFuture<Integer>> futures = views.stream()
            .map(view -> CompletableFuture.supplyAsync(
                () -> syncView(context.getExternalSystem(), view),
                threadPoolTaskExecutor
            ))
            .toList();

        // 3. 等待所有任务完成并汇总结果
        int totalSyncedBuilds = futures.stream()
            .map(future -> {
                try {
                    return future.get(5, TimeUnit.MINUTES);
                } catch (Exception e) {
                    log.error("Failed to get sync result", e);
                    return 0;
                }
            })
            .mapToInt(Integer::intValue)
            .sum();

        // 4. 更新同步历史
        updateSyncHistorySuccess(context.getSyncHistory());
        log.info("Successfully synchronized total {} builds for external system: {}",
            totalSyncedBuilds, context.getExternalSystem().getId());
        return totalSyncedBuilds;
    }

    @Transactional
    protected Integer syncView(ExternalSystem externalSystem, JenkinsView view) {
        try {
            // 1. 获取视图下的所有任务
            List<JenkinsJob> jobs = jenkinsJobRepository.findByExternalSystemIdAndViewId(
                externalSystem.getId(), view.getId());

            if (jobs.isEmpty()) {
                return 0;
            }

            // 2. 一次性获取视图下所有任务的最新状态
            List<JenkinsJobResponse> jobResponses = jenkinsServiceIntegration.listJobs(externalSystem, view.getViewName());
            if (jobResponses.isEmpty()) {
                log.debug("No job information available for view: {}", view.getViewName());
                return 0;
            }

            // 3. 批量查询所有任务的最新构建（解决N+1查询问题）
            List<Long> jobIds = jobs.stream().map(JenkinsJob::getId).toList();
            List<JenkinsBuild> latestBuilds = jenkinsBuildRepository.findLatestBuildsByJobIds(
                externalSystem.getId(), jobIds);
            Map<Long, JenkinsBuild> latestBuildMap = latestBuilds.stream()
                .collect(Collectors.toMap(JenkinsBuild::getJobId, build -> build));

            // 4. 创建任务名称到响应的映射
            Map<String, JenkinsJobResponse> jobResponseMap = jobResponses.stream()
                .collect(Collectors.toMap(JenkinsJobResponse::getName, job -> job));

            // 5. 同步每个任务的构建信息
            return jobs.stream()
                .map(job -> {
                    JenkinsJobResponse jobResponse = jobResponseMap.get(job.getJobName());
                    JenkinsBuild latestBuild = latestBuildMap.get(job.getId());
                    return syncJob(externalSystem, job, jobResponse, latestBuild);
                })
                .mapToInt(Integer::intValue)
                .sum();
        } catch (Exception e) {
            log.error("Failed to sync view: {}", view.getViewName(), e);
            return 0;
        }
    }

    @Transactional
    protected Integer syncJob(ExternalSystem externalSystem, JenkinsJob job, JenkinsJobResponse jobResponse) {
        return syncJob(externalSystem, job, jobResponse, null);
    }

    @Transactional
    protected Integer syncJob(ExternalSystem externalSystem, JenkinsJob job, JenkinsJobResponse jobResponse, JenkinsBuild latestBuild) {
        try {
            if (jobResponse == null || jobResponse.getLastBuild() == null) {
                log.debug("No build information available for job: {}", job.getJobName());
                return 0;
            }

            Integer latestBuildNumber = jobResponse.getLastBuild().getNumber();
            
            // 1. 使用预查询的最新构建或查询Build表中该Job的实际最大构建号
            Integer lastSyncedBuildNumber;
            if (latestBuild != null) {
                lastSyncedBuildNumber = latestBuild.getBuildNumber();
            } else {
                Optional<JenkinsBuild> build = jenkinsBuildRepository
                    .findTopByExternalSystemIdAndJobIdOrderByBuildNumberDesc(externalSystem.getId(), job.getId());
                lastSyncedBuildNumber = build.map(JenkinsBuild::getBuildNumber).orElse(null);
            }

            // 2. 判断是否有新构建（基于Build表实际数据判断）
            if (lastSyncedBuildNumber != null && latestBuildNumber <= lastSyncedBuildNumber) {
                // 没有新构建，直接返回（不打印日志，减少日志输出）
                return 0;
            }

            // 3. 确定同步范围
            Integer fromBuildNumber = (lastSyncedBuildNumber != null) ? lastSyncedBuildNumber + 1 : 1;

            // 4. 获取构建信息
            List<JenkinsBuildResponse> builds = jenkinsServiceIntegration.listBuilds(externalSystem, job.getJobName());

            // 5. 过滤出需要同步的构建
            List<JenkinsBuildResponse> newBuilds = builds.stream()
                .filter(build -> build.getNumber() >= fromBuildNumber && build.getNumber() <= latestBuildNumber)
                .collect(Collectors.toList());

            if (newBuilds.isEmpty()) {
                log.debug("No new builds found for job: {} (from: {}, to: {})", job.getJobName(), fromBuildNumber, latestBuildNumber);
                return 0;
            }

            // 6. 保存新的构建信息
            saveNewBuilds(externalSystem, job, newBuilds);

            // 7. 更新任务的最新构建信息
            updateJobLastBuild(job, jobResponse);

            log.info("Successfully synchronized {} builds for job: {} (from: {}, to: {})",
                newBuilds.size(), job.getJobName(), fromBuildNumber, latestBuildNumber);
            return newBuilds.size();
        } catch (Exception e) {
            log.error("Failed to sync job: {}", job.getJobName(), e);
            return 0;
        }
    }

    private void saveNewBuilds(
        ExternalSystem externalSystem,
        JenkinsJob job,
        List<JenkinsBuildResponse> builds) {
        List<JenkinsBuild> jenkinsBuilds = new ArrayList<>();

        for (JenkinsBuildResponse buildResponse : builds) {
            JenkinsBuild build = new JenkinsBuild();
            build.setExternalSystemId(externalSystem.getId());
            build.setJobId(job.getId());
            build.setBuildNumber(buildResponse.getNumber());

            try {
                // 调用 getBuildDetails 获取完整信息（包含 changeSets 等）
                JenkinsBuildResponse detailedBuild = jenkinsServiceIntegration.getBuildDetails(externalSystem, job.getJobName(), buildResponse.getNumber());
                if (detailedBuild != null) {
                    updateBuildFromResponse(build, detailedBuild);
                } else {
                    updateBuildFromResponse(build, buildResponse);
                }
            } catch (Exception e) {
                log.warn("获取构建详情失败，使用基础信息: job={}, build={}", job.getJobName(), buildResponse.getNumber());
                updateBuildFromResponse(build, buildResponse);
            }

            jenkinsBuilds.add(build);
        }

        log.debug("Saving {} builds for job: {}", jenkinsBuilds.size(), job.getJobName());
        jenkinsBuildRepository.saveAll(jenkinsBuilds);
    }

    private void updateJobLastBuild(JenkinsJob job, JenkinsJobResponse jobResponse) {
        job.setLastBuildNumber(jobResponse.getLastBuild().getNumber());
        if (jobResponse.getLastBuild().getTimestamp() != null) {
            job.setLastBuildTime(LocalDateTime.ofInstant(
                Instant.ofEpochMilli(jobResponse.getLastBuild().getTimestamp()),
                ZoneId.systemDefault()
            ));
        }
        jenkinsJobRepository.save(job);
    }

    private void handleSyncException(JenkinsSyncContext context, Exception e) {
        log.error("Failed to sync Jenkins builds for external system: {}", context.getExternalSystem().getId(), e);

        context.getSyncHistory().setStatus(ExternalSystemSyncStatus.FAILED);
        context.getSyncHistory().setErrorMessage(e.getMessage());
        jenkinsSyncHistoryService.saveOrUpdateHistory(context.getSyncHistory());
    }

    private void updateSyncHistorySuccess(JenkinsSyncHistoryDTO syncHistory) {
        syncHistory.setStatus(ExternalSystemSyncStatus.SUCCESS);
        jenkinsSyncHistoryService.saveOrUpdateHistory(syncHistory);
    }

    private void updateBuildFromResponse(JenkinsBuild jenkinsBuild, JenkinsBuildResponse response) {
        jenkinsBuild.setBuildUrl(response.getUrl());
        jenkinsBuild.setBuildStatus(response.getResult() != null ? response.getResult() : "BUILDING");
        jenkinsBuild.setDuration(response.getDuration());

        if (response.getTimestamp() != null) {
            LocalDateTime startTime = LocalDateTime.ofInstant(
                Instant.ofEpochMilli(response.getTimestamp()),
                ZoneId.systemDefault()
            );
            jenkinsBuild.setStarttime(startTime);
        }

        try {
            String changeSetsJson = objectMapper.writeValueAsString(response.getAllChangeSets());
            jenkinsBuild.setActions(changeSetsJson);
        } catch (JsonProcessingException e) {
            log.error("Failed to serialize changeSets", e);
            jenkinsBuild.setActions("[]");
        }
    }

    /**
     * 同步指定视图下的构建信息（异步执行）
     *
     * @param externalSystemId 外部系统ID
     * @param viewId           视图ID
     */
    @Override
    @Async
    @Transactional
    public void syncBuilds(Long externalSystemId, Long viewId) {
        doSyncBuilds(externalSystemId, viewId, null);
    }

    /**
     * 同步指定任务的构建信息（异步执行）
     *
     * @param externalSystemId 外部系统ID
     * @param viewId           视图ID (未使用，但为了保持API一致性)
     * @param jobId            任务ID
     */
    @Override
    @Async
    @Transactional
    public void syncBuilds(Long externalSystemId, Long viewId, Long jobId) {
        doSyncBuilds(externalSystemId, viewId, jobId);
    }

    /**
     * 执行构建同步的核心方法
     *
     * @param externalSystemId 外部系统ID
     * @param viewId           视图ID（可选）
     * @param jobId            任务ID（可选）
     */
    private void doSyncBuilds(Long externalSystemId, Long viewId, Long jobId) {
        // 1. 创建同步上下文
        JenkinsSyncContext context = createSyncContext(externalSystemId);

        try {
            // 2. 根据参数确定同步范围并执行
            if (jobId != null) {
                // 同步指定任务的构建
                JenkinsJob job = jenkinsJobRepository.findById(jobId)
                    .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND));
                JenkinsView view = jenkinsViewRepository.findById(job.getViewId())
                    .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND));
                // 获取该任务的最新状态
                List<JenkinsJobResponse> jobResponses = jenkinsServiceIntegration.listJobs(context.getExternalSystem(), view.getViewName());
                JenkinsJobResponse jobResponse = jobResponses.stream()
                    .filter(r -> r.getName().equals(job.getJobName()))
                    .findFirst()
                    .orElse(null);
                int syncedCount = syncJob(context.getExternalSystem(), job, jobResponse);
                log.debug("Synchronized {} builds for job: {}", syncedCount, job.getJobName());
            } else if (viewId != null) {
                // 同步指定视图下的所有构建
                JenkinsView view = jenkinsViewRepository.findById(viewId)
                    .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND));
                int syncedCount = syncView(context.getExternalSystem(), view);
                log.debug("Synchronized {} builds for view: {}", syncedCount, view.getViewName());
            } else {
                // 全量同步
                doSync(context);
            }

            // 3. 更新同步历史为成功
            updateSyncHistorySuccess(context.getSyncHistory());
            log.info("Successfully synchronized builds for external system: {}", externalSystemId);
        } catch (Exception e) {
            handleSyncException(context, e);
            throw e;
        }
    }

    /**
     * 检查并发送构建通知（参考longi-deployment逻辑重构）
     *
     * <p>核心流程：
     * <ol>
     *   <li>获取所有绑定了该Jenkins系统的TeamApplication</li>
     *   <li>按 deployJob 分组，遍历每个 Job</li>
     *   <li>获取该 Job 的最新构建记录</li>
     *   <li>查通知记录表，判断是否需要发送通知</li>
     * </ol>
     */
    private void checkBuildNotifications(Long externalSystemId) {
        log.debug("开始检查构建通知: externalSystemId={}", externalSystemId);

        try {
            // 1. 获取外部系统信息
            ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId).orElse(null);
            if (externalSystem == null) {
                log.warn("外部系统不存在: externalSystemId={}", externalSystemId);
                return;
            }

            // 2. 查询绑定该Jenkins系统的TeamApplication（构建类型为JENKINS）
            List<TeamApplication> teamApps = teamApplicationRepository
                .findByDeploySystemIdAndBuildType(externalSystemId, BuildTypeEnum.JENKINS);
            if (teamApps.isEmpty()) {
                log.debug("没有团队绑定该Jenkins系统: externalSystemId={}", externalSystemId);
                return;
            }

            // 3. 按 deployJob 分组（过滤掉空的deployJob）
            Map<String, List<TeamApplication>> teamAppsByJob = teamApps.stream()
                .filter(ta -> ta.getDeployJob() != null && !ta.getDeployJob().isEmpty())
                .collect(Collectors.groupingBy(TeamApplication::getDeployJob));
            if (teamAppsByJob.isEmpty()) {
                log.debug("没有配置deployJob的团队应用");
                return;
            }

            // 4. 查询启用了构建通知的团队环境配置
            Set<Long> teamIds = teamApps.stream().map(TeamApplication::getTeamId).collect(Collectors.toSet());
            Set<Long> envIds = teamApps.stream().map(TeamApplication::getEnvironmentId).collect(Collectors.toSet());

            List<TeamEnvironmentNotificationConfig> configs = teamEnvironmentNotificationConfigRepository
                .findByTeamIdInAndEnvironmentIdInAndBuildNotificationEnabledTrue(teamIds, envIds);
            if (configs.isEmpty()) {
                log.debug("没有团队启用构建通知");
                return;
            }

            // 5. 构建配置Map（key: teamId_envId）
            Map<String, TeamEnvironmentNotificationConfig> configMap = configs.stream()
                .collect(Collectors.toMap(
                    cfg -> cfg.getTeamId() + "_" + cfg.getEnvironmentId(),
                    cfg -> cfg
                ));

            // 6. 批量查询通知渠道
            Set<Long> channelIds = configs.stream()
                .map(TeamEnvironmentNotificationConfig::getNotificationChannelId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
            Map<Long, NotificationChannel> channelMap = notificationChannelRepository.findAllById(channelIds)
                .stream().collect(Collectors.toMap(NotificationChannel::getId, c -> c));

            // 7. 批量查询应用和环境信息
            Set<Long> applicationIds = teamApps.stream().map(TeamApplication::getApplicationId).collect(Collectors.toSet());
            Map<Long, Application> applicationMap = applicationRepository.findAllById(applicationIds)
                .stream().collect(Collectors.toMap(Application::getId, a -> a));
            Map<Long, Environment> environmentMap = environmentRepository.findAllById(envIds)
                .stream().collect(Collectors.toMap(Environment::getId, e -> e));

            // 8. 批量查询所有视图下的Job，构建 jobName -> JenkinsJob 的Map
            List<JenkinsView> views = jenkinsViewRepository.findByExternalSystemId(externalSystemId);
            Map<String, JenkinsJob> jobByNameMap = new HashMap<>();
            for (JenkinsView view : views) {
                List<JenkinsJob> jobs = jenkinsJobRepository.findByExternalSystemIdAndViewId(externalSystemId, view.getId());
                for (JenkinsJob job : jobs) {
                    jobByNameMap.put(job.getJobName(), job);
                }
            }

            // 9. 收集需要处理的Job及其最新构建
            Map<Long, JenkinsBuild> latestBuildByJobId = new HashMap<>();
            Set<String> jobNamesToProcess = teamAppsByJob.keySet();
            for (String jobName : jobNamesToProcess) {
                JenkinsJob job = jobByNameMap.get(jobName);
                if (job == null) {
                    log.debug("Job不存在: jobName={}", jobName);
                    continue;
                }
                jenkinsBuildRepository.findTopByExternalSystemIdAndJobIdOrderByBuildNumberDesc(externalSystemId, job.getId())
                    .ifPresent(build -> latestBuildByJobId.put(job.getId(), build));
            }

            if (latestBuildByJobId.isEmpty()) {
                log.debug("没有需要处理的构建记录");
                return;
            }

            // 10. 批量查询通知记录（避免N+1）
            Set<Long> buildIds = latestBuildByJobId.values().stream()
                .map(JenkinsBuild::getId)
                .collect(Collectors.toSet());
            List<JenkinsBuildNotification> existingNotifications = jenkinsBuildNotificationRepository
                .findByBuildIdInAndDeletedFalse(buildIds);
            // 构建 buildId_teamId_envId -> notification 的Map
            Map<String, JenkinsBuildNotification> notificationMap = existingNotifications.stream()
                .collect(Collectors.toMap(
                    n -> n.getBuildId() + "_" + n.getTeamId() + "_" + n.getEnvironmentId(),
                    n -> n
                ));

            // 11. 遍历每个 Job，处理通知
            for (Map.Entry<String, List<TeamApplication>> entry : teamAppsByJob.entrySet()) {
                String jobName = entry.getKey();
                List<TeamApplication> relatedTeamApps = entry.getValue();

                JenkinsJob job = jobByNameMap.get(jobName);
                if (job == null) continue;

                JenkinsBuild latestBuild = latestBuildByJobId.get(job.getId());
                if (latestBuild == null) continue;

                // 获取构建状态：只有 BUILDING 状态才需要调用 API 刷新
                String latestStatus = latestBuild.getBuildStatus();

                if ("BUILDING".equals(latestStatus)) {
                    // 构建进行中，需要从 Jenkins API 获取最新状态
                    try {
                        JenkinsBuildResponse latestBuildInfo = jenkinsServiceIntegration.getBuildDetails(externalSystem, job.getJobName(), latestBuild.getBuildNumber());
                        if (latestBuildInfo != null && latestBuildInfo.getResult() != null) {
                            latestStatus = latestBuildInfo.getResult();
                            // 状态有变化，更新数据库
                            updateBuildFromResponse(latestBuild, latestBuildInfo);
                            jenkinsBuildRepository.save(latestBuild);
                            log.info("构建状态已更新: job={}, build={}, status={}", job.getJobName(), latestBuild.getBuildNumber(), latestStatus);
                        }
                    } catch (Exception e) {
                        log.warn("从Jenkins获取构建信息失败，跳过本次处理，等待下次重试: job={}, buildNumber={}, error={}", job.getJobName(), latestBuild.getBuildNumber(), e.getMessage());
                        continue;  // 跳过这个 Job，下次定时任务重试
                    }
                }
                // 已完成的构建（SUCCESS/FAILURE/ABORTED）直接使用数据库中的状态，不调用 API

                // 处理每个关联的 TeamApplication
                for (TeamApplication teamApp : relatedTeamApps) {
                    String configKey = teamApp.getTeamId() + "_" + teamApp.getEnvironmentId();
                    TeamEnvironmentNotificationConfig config = configMap.get(configKey);
                    if (config == null) continue;

                    NotificationChannel channel = channelMap.get(config.getNotificationChannelId());
                    if (channel == null) continue;

                    Application application = applicationMap.get(teamApp.getApplicationId());
                    Environment environment = environmentMap.get(teamApp.getEnvironmentId());

                    // 从Map中获取通知记录（避免循环内查询）
                    String notificationKey = latestBuild.getId() + "_" + config.getTeamId() + "_" + config.getEnvironmentId();
                    JenkinsBuildNotification existingRecord = notificationMap.get(notificationKey);

                    // 处理该构建的通知
                    processBuildNotification(config, channel, job, latestBuild, latestStatus,
                        externalSystem, application, environment, existingRecord);
                }
            }

        } catch (Exception e) {
            log.error("检查构建通知失败: externalSystemId={}", externalSystemId, e);
        }
    }

    /**
     * 处理单个构建的通知（参考longi-deployment逻辑）
     *
     * <p>核心逻辑：
     * <ul>
     *   <li>无记录 = 新构建：超时(>20分钟)直接标记完成，未超时发送"构建中"通知</li>
     *   <li>有记录但buildEndNotice=false：检查是否完成，发送结束通知</li>
     * </ul>
     */
    private void processBuildNotification(TeamEnvironmentNotificationConfig config, NotificationChannel channel, JenkinsJob job, JenkinsBuild build, String latestStatus, ExternalSystem externalSystem, Application application, Environment environment, JenkinsBuildNotification existingRecord) {

        try {
            // 计算构建开始到现在的分钟数
            long minutesAgo = ChronoUnit.MINUTES.between(build.getStarttime(), LocalDateTime.now());

            // 1. 新构建（通知记录不存在）
            if (existingRecord == null) {
                // 超过20分钟的旧构建，直接标记完成，不发通知
                if (minutesAgo > 20) {
                    JenkinsBuildNotification record = new JenkinsBuildNotification();
                    record.setBuildId(build.getId());
                    record.setTeamId(config.getTeamId());
                    record.setEnvironmentId(config.getEnvironmentId());
                    record.setBuildStartNotice(true);
                    record.setBuildEndNotice(true);  // 直接标记结束
                    jenkinsBuildNotificationRepository.save(record);
                    log.info("构建超时，直接标记完成: job={}, build={}, minutesAgo={}", job.getJobName(), build.getBuildNumber(), minutesAgo);
                    return;
                }

                // 未超时，发送"构建中"通知
                JenkinsBuildNotification record = new JenkinsBuildNotification();
                record.setBuildId(build.getId());
                record.setTeamId(config.getTeamId());
                record.setEnvironmentId(config.getEnvironmentId());
                sendNotification(config, channel, job, build, "BUILDING", externalSystem, application, environment);
                record.setBuildStartNotice(true);
                record.setBuildEndNotice(false);
                jenkinsBuildNotificationRepository.save(record);
                log.info("发送构建开始通知: job={}, build={}", job.getJobName(), build.getBuildNumber());
                return;
            }

            // 2. 已有记录，检查是否需要发送结束通知
            if (!existingRecord.getBuildEndNotice()) {
                if (isStatusFinished(latestStatus)) {
                    // 构建已完成，发送结束通知
                    sendNotification(config, channel, job, build, latestStatus, externalSystem, application, environment);
                    existingRecord.setBuildEndNotice(true);
                    jenkinsBuildNotificationRepository.save(existingRecord);
                    log.info("发送构建结束通知: job={}, build={}, status={}", job.getJobName(), build.getBuildNumber(), latestStatus);
                } else {
                    // 兜底：超过2小时仍未完成，强制标记结束（不发通知）
                    long hoursAgo = ChronoUnit.HOURS.between(build.getStarttime(), LocalDateTime.now());
                    if (hoursAgo >= 2) {
                        log.warn("构建超时未完成，强制标记结束: job={}, build={}, startTime={}", job.getJobName(), build.getBuildNumber(), build.getStarttime());
                        existingRecord.setBuildEndNotice(true);
                        jenkinsBuildNotificationRepository.save(existingRecord);
                    }
                }
            }

        } catch (ObjectOptimisticLockingFailureException e) {
            log.warn("构建通知记录乐观锁冲突，跳过: teamId={}, envId={}, buildId={}", config.getTeamId(), config.getEnvironmentId(), build.getId());
        } catch (Exception e) {
            log.error("处理构建通知失败: teamId={}, envId={}, buildId={}", config.getTeamId(), config.getEnvironmentId(), build.getId(), e);
        }
    }

    /**
     * 判断状态是否为已完成
     */
    private boolean isStatusFinished(String status) {
        return "SUCCESS".equals(status) || "FAILURE".equals(status) || "ABORTED".equals(status);
    }

    /**
     * 发送通知（使用模板）
     */
    private void sendNotification(TeamEnvironmentNotificationConfig config, NotificationChannel channel, JenkinsJob job, JenkinsBuild build, String status, ExternalSystem externalSystem, Application application, Environment environment) {

        try {
            // 1. 检查是否配置了构建通知模板
            if (config.getBuildNotificationTemplateId() == null) {
                log.warn("未配置构建通知模板，跳过通知: teamId={}, envId={}", config.getTeamId(), config.getEnvironmentId());
                return;
            }

            // 2. 查询模板
            NotificationTemplate template = notificationTemplateRepository.findById(config.getBuildNotificationTemplateId()).orElse(null);
            if (template == null) {
                log.warn("构建通知模板不存在: templateId={}", config.getBuildNotificationTemplateId());
                return;
            }

            // 3. 构建模板参数
            Map<String, Object> templateParams = new HashMap<>();

            // 应用信息
            if (application != null) {
                templateParams.put("applicationId", application.getId());
                templateParams.put("applicationCode", application.getAppCode());
                templateParams.put("applicationName", application.getAppName());
            }

            // 环境信息
            if (environment != null) {
                templateParams.put("environmentId", environment.getId());
                templateParams.put("environmentCode", environment.getEnvCode());
                templateParams.put("environmentName", environment.getEnvName());
            }

            // Jenkins 构建信息
            templateParams.put("jobName", job.getJobName());
            templateParams.put("buildNumber", build.getBuildNumber());
            templateParams.put("buildUrl", build.getBuildUrl());
            templateParams.put("buildStatus", status);

            // 时间格式化
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            if (build.getStarttime() != null) {
                templateParams.put("buildStartTime", build.getStarttime().format(formatter));
            }

            // 构建结束时间（如果有）
            if (build.getStarttime() != null && build.getDuration() != null) {
                LocalDateTime endTime = build.getStarttime().plusNanos(build.getDuration() * 1_000_000);
                templateParams.put("buildEndTime", endTime.format(formatter));
            }

            // 耗时格式化
            if (build.getDuration() != null) {
                long totalSeconds = build.getDuration() / 1000;
                long minutes = totalSeconds / 60;
                long seconds = totalSeconds % 60;
                templateParams.put("buildDurationFormatted", String.format("%d分%d秒", minutes, seconds));
                templateParams.put("buildDurationMs", build.getDuration());
            }

            // 从 actions JSON 中解析 commit 信息
            parseCommitInfoFromActions(build.getActions(), templateParams);

            // 4. 构建 SendNotificationRequest
            SendNotificationRequest request = new SendNotificationRequest();
            request.setChannelId(channel.getId());
            request.setNotificationTemplateId(config.getBuildNotificationTemplateId());
            request.setTemplateParams(templateParams);

            log.debug("准备发送构建通知: job={}, build={}, templateId={}, channelId={}", job.getJobName(), build.getBuildNumber(), config.getBuildNotificationTemplateId(), channel.getId());

            // 5. 发送通知（NotificationService会自动根据渠道类型创建请求对象）
            notificationService.send(request);

            log.info("已发送构建通知: job={}, build={}, status={}, templateId={}", job.getJobName(), build.getBuildNumber(), status, config.getBuildNotificationTemplateId());

            // 6. 构建失败时，发送日志文件（如果开启）
            if ("FAILURE".equals(status) && Boolean.TRUE.equals(config.getBuildFailureFileEnabled())) {
                sendBuildFailureLogFile(externalSystem, channel, job.getJobName(), build.getBuildNumber());
            }

        } catch (Exception e) {
            log.error("发送通知失败: job={}, build={}", job.getJobName(), build.getBuildNumber(), e);
        }
    }

    /**
     * 从 changeSets JSON 中解析 commit 信息
     */
    private void parseCommitInfoFromActions(String changeSetsJson, Map<String, Object> templateParams) {
        if (changeSetsJson == null || changeSetsJson.isEmpty()) {
            return;
        }
        try {
            // 直接反序列化为 List<ChangeSet>
            List<JenkinsBuildResponse.ChangeSet> changeSets = objectMapper.readValue(
                changeSetsJson,
                new com.fasterxml.jackson.core.type.TypeReference<List<JenkinsBuildResponse.ChangeSet>>() {
                }
            );

            if (changeSets == null || changeSets.isEmpty()) {
                return;
            }

            StringBuilder commitMessages = new StringBuilder();
            for (JenkinsBuildResponse.ChangeSet changeSet : changeSets) {
                if (changeSet.getItems() == null) continue;
                for (JenkinsBuildResponse.ChangeSetItem item : changeSet.getItems()) {
                    if (item.getMsg() == null || item.getMsg().isEmpty()) continue;

                    if (commitMessages.length() > 0) {
                        commitMessages.append("\n");
                    }
                    String commitId = item.getCommitId() != null ? item.getCommitId() : "";
                    String shortCommitId = commitId.length() > 8 ? commitId.substring(0, 8) : commitId;
                    String author = item.getAuthor() != null && item.getAuthor().getFullName() != null
                        ? item.getAuthor().getFullName() : "";
                    commitMessages.append(String.format("[%s] %s - %s", shortCommitId, item.getMsg().trim(), author));
                }
            }

            if (commitMessages.length() > 0) {
                templateParams.put("commitMessage", commitMessages.toString());
            }
        } catch (Exception e) {
            log.error("解析 commit 信息失败", e);
        }
    }

    /**
     * 发送构建失败日志文件到企业微信
     */
    private void sendBuildFailureLogFile(ExternalSystem externalSystem, NotificationChannel channel,
                                         String jobName, Integer buildNumber) {
        try {
            log.info("开始下载构建失败日志: job={}, buildNumber={}", jobName, buildNumber);

            // 1. 下载日志文件到临时目录
            String logFilePath = downloadBuildLogFile(externalSystem, jobName, buildNumber);
            if (logFilePath == null) {
                log.warn("日志文件下载失败，跳过文件发送");
                return;
            }

            // 2. 上传文件到企业微信
            String mediaId = uploadFileToWechat(channel, logFilePath);
            if (mediaId == null) {
                log.warn("文件上传到企业微信失败");
                return;
            }

            // 3. 发送文件消息
            sendWechatFileMessage(channel, mediaId);

            // 4. 清理临时文件
            cleanupTempFile(logFilePath);

            log.info("构建失败日志文件发送成功: job={}, buildNumber={}", jobName, buildNumber);

        } catch (Exception e) {
            log.error("发送构建失败日志文件失败: job={}, buildNumber={}", jobName, buildNumber, e);
        }
    }

    /**
     * 下载构建日志文件
     */
    private String downloadBuildLogFile(ExternalSystem externalSystem, String jobName, Integer buildNumber) {
        try {
            // 获取完整的控制台输出
            var consoleOutput = jenkinsServiceIntegration.getConsoleOutput(externalSystem, jobName, buildNumber, 0L);

            if (consoleOutput == null || consoleOutput.getLines() == null || consoleOutput.getLines().isEmpty()) {
                return null;
            }

            // 创建临时文件
            String tempDir = System.getProperty("java.io.tmpdir");
            String fileName = String.format("jenkins-build-%s-%d.log", jobName, buildNumber);
            Path filePath = Paths.get(tempDir, fileName);

            // 写入日志内容
            Files.write(filePath, consoleOutput.getLines());

            log.info("日志文件已下载: {}", filePath);
            return filePath.toString();

        } catch (Exception e) {
            log.error("下载构建日志文件失败: job={}, buildNumber={}",
                jobName, buildNumber, e);
            return null;
        }
    }

    /**
     * 上传文件到企业微信
     */
    private String uploadFileToWechat(NotificationChannel channel, String filePath) {
        try {
            // 从 config 中获取 key
            if (channel.getConfig() == null) {
                log.error("企业微信渠道配置为空: channelId={}", channel.getId());
                return null;
            }
            String key = (String) channel.getConfig().get("key");
            if (key == null || key.isEmpty()) {
                log.error("企业微信渠道 key 为空: channelId={}", channel.getId());
                return null;
            }

            // 企业微信文件上传接口
            String uploadUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key=" + key + "&type=file";

            // 构建 multipart/form-data 请求
            FileSystemResource fileResource = new FileSystemResource(filePath);

            LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
            map.add("media", fileResource);

            HttpHeaders headers = new org.springframework.http.HttpHeaders();
            headers.setContentType(org.springframework.http.MediaType.MULTIPART_FORM_DATA);

            HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(map, headers);

            RestTemplate restTemplate = new org.springframework.web.client.RestTemplate();
            ResponseEntity<String> response = restTemplate.postForEntity(uploadUrl, requestEntity, String.class);

            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                // 解析响应，获取 media_id
                JsonNode jsonNode = JsonUtils.parseJson(response.getBody());
                if (jsonNode == null) {
                    log.error("文件上传响应解析失败");
                    return null;
                }

                int errcode = jsonNode.get("errcode").asInt();
                if (errcode == 0) {
                    String mediaId = jsonNode.get("media_id").asText();
                    log.info("文件上传成功，media_id: {}", mediaId);
                    return mediaId;
                } else {
                    log.error("文件上传失败，errcode: {}, errmsg: {}",
                        errcode, jsonNode.get("errmsg").asText());
                    return null;
                }
            }

            return null;

        } catch (Exception e) {
            log.error("上传文件到企业微信失败: filePath={}", filePath, e);
            return null;
        }
    }

    /**
     * 发送企业微信文件消息
     */
    private void sendWechatFileMessage(NotificationChannel channel, String mediaId) {
        try {
            String key = (String) channel.getConfig().get("key");
            String webhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=" + key;

            // 构建文件消息
            Map<String, Object> message = new HashMap<>();
            message.put("msgtype", "file");

            Map<String, String> file = new HashMap<>();
            file.put("media_id", mediaId);
            message.put("file", file);

            // 发送请求
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(message, headers);

            RestTemplate restTemplate = new RestTemplate();
            ResponseEntity<String> response = restTemplate.postForEntity(webhookUrl, requestEntity, String.class);

            if (response.getStatusCode().is2xxSuccessful()) {
                log.info("企业微信文件消息发送成功");
            } else {
                log.error("企业微信文件消息发送失败: {}", response.getBody());
            }

        } catch (Exception e) {
            log.error("发送企业微信文件消息失败: mediaId={}", mediaId, e);
        }
    }

    /**
     * 清理临时文件
     */
    private void cleanupTempFile(String filePath) {
        try {
            Files.deleteIfExists(Paths.get(filePath));
            log.debug("临时文件已清理: {}", filePath);
        } catch (Exception e) {
            log.warn("清理临时文件失败: {}", filePath, e);
        }
    }

    @Override
    public Long countByExternalSystemId(Long externalSystemId) {
        QJenkinsBuild qJenkinsBuild = QJenkinsBuild.jenkinsBuild;
        return super.repository.count(
            qJenkinsBuild.externalSystemId.eq(externalSystemId)
                .and(qJenkinsBuild.deleted.eq(false))
        );
    }
}