package com.qqchen.deploy.backend.workflow.delegate;

import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
import com.qqchen.deploy.backend.deploy.enums.JenkinsBuildStatus;
import com.qqchen.deploy.backend.deploy.integration.IJenkinsServiceIntegration;
import com.qqchen.deploy.backend.deploy.integration.response.JenkinsConsoleOutputResponse;
import com.qqchen.deploy.backend.deploy.integration.response.JenkinsBuildResponse;
import com.qqchen.deploy.backend.deploy.integration.response.JenkinsQueueBuildInfoResponse;
import com.qqchen.deploy.backend.deploy.repository.IExternalSystemRepository;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.utils.DateUtils;
import com.qqchen.deploy.backend.workflow.dto.inputmapping.JenkinsBuildInputMapping;
import com.qqchen.deploy.backend.workflow.dto.outputs.JenkinsBuildOutputs;
import com.qqchen.deploy.backend.workflow.enums.LogLevel;
import com.qqchen.deploy.backend.workflow.enums.LogSource;
import com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum;
import com.qqchen.deploy.backend.workflow.service.IWorkflowNodeLogService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.ResourceAccessException;

import java.util.HashMap;
import java.util.Map;

/**
 * Jenkins构建任务委派
 *
 * @author qqchen
 * @since 2025-10-22
 */
@Slf4j
@Component("jenkinsBuildDelegate")
public class JenkinsBuildDelegate extends BaseNodeDelegate<JenkinsBuildInputMapping, JenkinsBuildOutputs> {

    @Resource
    private IJenkinsServiceIntegration jenkinsServiceIntegration;

    @Resource
    private IExternalSystemRepository externalSystemRepository;


    @Resource
    private IWorkflowNodeLogService workflowNodeLogService;

    private static final int QUEUE_POLL_INTERVAL = 10; // 10秒

    private static final int MAX_QUEUE_POLLS = 30; // 最多等待5分钟

    private static final int BUILD_POLL_INTERVAL = 10; // 轮询间隔（秒）

    private static final int MAX_BUILD_POLLS = 180; // 30分钟超时

    @Override
    protected void executeInternal(DelegateExecution execution, Map<String, Object> configs, JenkinsBuildInputMapping input) {
        try {
            log.info("Jenkins Build - serverId: {}, jobName: {}", input.getServerId(), input.getJobName());

            // 1. 获取外部系统（配置错误 - 技术异常）
            ExternalSystem externalSystem = externalSystemRepository.findById(input.getServerId())
                .orElseThrow(() -> new BusinessException(ResponseCode.JENKINS_SERVER_NOT_FOUND, new Object[]{input.getServerId()}));

            String jobName = input.getJobName();

            // 2. 触发构建
            Map<String, String> parameters = new HashMap<>();
            // 可以根据需要添加构建参数
            // parameters.put("BRANCH", "main");

            String queueId = jenkinsServiceIntegration.buildWithParameters(externalSystem, jobName, parameters);

            log.info("Jenkins build queued: queueId={}", queueId);

            // 3. 等待构建从队列中开始
            JenkinsQueueBuildInfoResponse buildInfo = waitForBuildToStart(externalSystem, queueId);

            log.info("Jenkins build started: buildNumber={}", buildInfo.getBuildNumber());

            // 4. 记录构建启动日志
            workflowNodeLogService.info(execution.getProcessInstanceId(), execution.getCurrentActivityId(), LogSource.JENKINS, 
                String.format("Jenkins 构建已启动: job=%s, buildNumber=%d", jobName, buildInfo.getBuildNumber()));

            // 5. 轮询构建状态直到完成
            JenkinsBuildStatus buildStatus = pollBuildStatus(execution, externalSystem, jobName, buildInfo.getBuildNumber());

            // 6. 获取构建详细信息（包括 duration, changeSets, artifacts）
            JenkinsBuildResponse buildDetails = jenkinsServiceIntegration.getBuildDetails(externalSystem, jobName, buildInfo.getBuildNumber());

            // 打印调试信息（仅数量，避免大对象噪音）
            int changeSetItemsCount = (buildDetails.getChangeSet() != null && buildDetails.getChangeSet().getItems() != null) 
                ? buildDetails.getChangeSet().getItems().size() : 0;
            int artifactsCount = (buildDetails.getArtifacts() != null) ? buildDetails.getArtifacts().size() : 0;
            log.info("Build details - changeSetItemsCount={}, artifactsCount={}", changeSetItemsCount, artifactsCount);

            // 7. 设置输出结果（包含节点状态）
            fillOutputsFrom(buildInfo, buildDetails, buildStatus, output);

            // 8. 记录完成日志
            String logMessage = buildStatus == JenkinsBuildStatus.SUCCESS 
                ? "Jenkins 构建任务执行完成：构建成功"
                : String.format("Jenkins 构建任务执行完成：%s", buildStatus.getDescription());
            workflowNodeLogService.info(execution.getProcessInstanceId(), execution.getCurrentActivityId(), LogSource.JENKINS, logMessage);

            // ✅ 正常返回，基类会根据 output.status + continueOnFailure 决定是否抛 BpmnError
            
        } catch (ResourceAccessException e) {
            // 网络连接异常 - 技术异常，强制失败
            log.error("Jenkins 连接失败: serverId={}, error={}", input.getServerId(), e.getMessage(), e);
            throw new BusinessException(ResponseCode.JENKINS_CONNECTION_FAILED, new Object[]{input.getServerId()});
            
        } catch (HttpClientErrorException.Unauthorized | HttpClientErrorException.Forbidden e) {
            // 认证失败 - 技术异常，强制失败
            log.error("Jenkins 认证失败: serverId={}, status={}", input.getServerId(), e.getStatusCode());
            throw new BusinessException(ResponseCode.JENKINS_AUTH_FAILED, new Object[]{input.getServerId()});
            
        } catch (HttpClientErrorException.NotFound e) {
            // Job不存在 - 技术异常，强制失败
            log.error("Jenkins Job 不存在: jobName={}", input.getJobName());
            throw new BusinessException(ResponseCode.JENKINS_JOB_NOT_FOUND, new Object[]{input.getJobName()});
            
        } catch (HttpServerErrorException e) {
            // Jenkins服务器错误 - 技术异常，强制失败
            log.error("Jenkins 服务器错误: serverId={}, status={}", input.getServerId(), e.getStatusCode());
            throw new BusinessException(ResponseCode.JENKINS_SERVER_ERROR, new Object[]{e.getStatusCode().value()});
        }
    }

    private JenkinsQueueBuildInfoResponse waitForBuildToStart(ExternalSystem externalSystem, String queueId) {
        int attempts = 0;
        while (attempts < MAX_QUEUE_POLLS) {
            try {
                JenkinsQueueBuildInfoResponse buildInfo = jenkinsServiceIntegration.getQueuedBuildInfo(externalSystem, queueId);
                if (buildInfo != null) {
                    return buildInfo;
                }

                log.debug("Build still in queue, waiting... attempts: {}/{}", attempts + 1, MAX_QUEUE_POLLS);

                Thread.sleep(QUEUE_POLL_INTERVAL * 1000L);
                attempts++;

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Interrupted while waiting for build to start", e);
            }
        }

        throw new BusinessException(ResponseCode.JENKINS_QUEUE_TIMEOUT, new Object[]{MAX_QUEUE_POLLS * QUEUE_POLL_INTERVAL});
    }

    private JenkinsBuildStatus pollBuildStatus(DelegateExecution execution, ExternalSystem externalSystem, String jobName, Integer buildNumber) {
        int attempts = 0;
        long logOffset = 0L;  // 记录日志读取位置

        while (attempts < MAX_BUILD_POLLS) {
            try {
                // 等待一定时间后再检查
                Thread.sleep(BUILD_POLL_INTERVAL * 1000L);

                // 1. 增量拉取并保存 Jenkins 构建日志
                try {
                    JenkinsConsoleOutputResponse consoleOutput = jenkinsServiceIntegration.getConsoleOutput(externalSystem, jobName, buildNumber, logOffset);

                    // 批量保存日志到数据库（同时也输出到控制台）
                    if (consoleOutput.getLines() != null && !consoleOutput.getLines().isEmpty()) {
                        // 保存到数据库
                        workflowNodeLogService.batchLog(execution.getProcessInstanceId(), execution.getCurrentActivityId(), LogSource.JENKINS, LogLevel.INFO, consoleOutput.getLines());
                    }
                    // 更新日志偏移量
                    logOffset = consoleOutput.getNextOffset();

                } catch (Exception logEx) {
                    log.warn("Failed to fetch Jenkins console log, continuing: {}", logEx.getMessage());
                    workflowNodeLogService.warn(execution.getProcessInstanceId(), execution.getCurrentActivityId(), LogSource.JENKINS, "获取 Jenkins 日志失败: " + logEx.getMessage());
                }

                // ✅ 2. 获取构建状态
                JenkinsBuildStatus status = jenkinsServiceIntegration.getBuildStatus(externalSystem, jobName, buildNumber);
                log.info("Jenkins build status: job={}, buildNumber={}, status={}", jobName, buildNumber, status);

                if (status == JenkinsBuildStatus.IN_PROGRESS) {
                    // 继续轮询
                    attempts++;
                    continue;
                }

                // 统一处理终止态（SUCCESS/FAILURE/ABORTED/NOT_FOUND）
                if (needsFetchLogs(status)) {
                    fetchRemainingLogs(execution, externalSystem, jobName, buildNumber, logOffset);
                }
                logTerminalStatus(status, jobName, buildNumber);
                return status;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Build status polling was interrupted", e);
            }
        }
        throw new BusinessException(ResponseCode.JENKINS_BUILD_TIMEOUT, new Object[]{jobName, buildNumber, MAX_BUILD_POLLS * BUILD_POLL_INTERVAL / 60});
    }

    /**
     * 拉取剩余的日志（构建完成时调用）
     */
    private void fetchRemainingLogs(DelegateExecution execution, ExternalSystem externalSystem, String jobName, Integer buildNumber, long lastOffset) {
        try {
            JenkinsConsoleOutputResponse consoleOutput = jenkinsServiceIntegration.getConsoleOutput(externalSystem, jobName, buildNumber, lastOffset);

            if (consoleOutput.getLines() != null && !consoleOutput.getLines().isEmpty()) {
                // 保存到数据库
                workflowNodeLogService.batchLog(execution.getProcessInstanceId(), execution.getCurrentActivityId(), LogSource.JENKINS, LogLevel.INFO, consoleOutput.getLines());
            }

            log.info("Jenkins build log complete: job={}, buildNumber={}", jobName, buildNumber);
            workflowNodeLogService.info(execution.getProcessInstanceId(), execution.getCurrentActivityId(), LogSource.JENKINS, "Jenkins 构建日志已完整收集");

        } catch (Exception e) {
            log.warn("Failed to fetch remaining Jenkins logs: {}", e.getMessage());
            workflowNodeLogService.warn(execution.getProcessInstanceId(), execution.getCurrentActivityId(), LogSource.JENKINS, "获取剩余日志失败: " + e.getMessage());
        }
    }

    /**
     * 终止状态是否需要抓取剩余日志
     */
    private boolean needsFetchLogs(JenkinsBuildStatus status) {
        return status == JenkinsBuildStatus.SUCCESS
            || status == JenkinsBuildStatus.FAILURE
            || status == JenkinsBuildStatus.ABORTED;
    }

    /**
     * 统一终止态日志输出
     */
    private void logTerminalStatus(JenkinsBuildStatus status, String jobName, Integer buildNumber) {
        switch (status) {
            case SUCCESS -> {
                log.info("Jenkins build succeeded: job={}, buildNumber={}", jobName, buildNumber);
                logInfo(String.format("✅ Jenkins 构建成功: buildNumber=%d", buildNumber));
            }
            case FAILURE -> {
                log.info("Jenkins build failed: job={}, buildNumber={}", jobName, buildNumber);
                logInfo(String.format("❌ Jenkins 构建失败: buildNumber=%d", buildNumber));
            }
            case ABORTED -> {
                log.info("Jenkins build aborted: job={}, buildNumber={}", jobName, buildNumber);
                logInfo(String.format("⏹ Jenkins 构建被取消: buildNumber=%d", buildNumber));
            }
            case NOT_FOUND -> {
                log.warn("Jenkins build not found: job={}, buildNumber={}", jobName, buildNumber);
                logInfo(String.format("⚠ Jenkins 构建记录不存在: buildNumber=%d", buildNumber));
            }
        }
    }

    /**
     * 将服务层返回的构建详情填充到输出对象
     */
    private void fillOutputsFrom(JenkinsQueueBuildInfoResponse buildInfo, JenkinsBuildResponse buildDetails, JenkinsBuildStatus buildStatus, JenkinsBuildOutputs output) {
        // Jenkins 基本信息
        output.setBuildStatus(buildStatus);
        output.setBuildNumber(buildInfo.getBuildNumber());
        output.setBuildUrl(buildInfo.getBuildUrl());

        // 构建时长（毫秒与秒）
        long durationMs = buildDetails.getDuration() != null ? Math.max(0L, buildDetails.getDuration()) : 0L;
        int durationSeconds = (int) Math.min(Integer.MAX_VALUE, durationMs / 1000L);
        output.setBuildDuration(durationSeconds);
        output.setBuildDurationMillis(durationMs);
        output.setBuildDurationFormatted(DateUtils.formatDurationHMS(durationMs));

        // 结束时间（服务层已计算）
        output.setBuildEndTimeMillis(buildDetails.getEndTimeMillis());
        output.setBuildEndTime(buildDetails.getEndTime());

        // Git 提交ID（从 changeSets 中获取最后一个 commit）
        String gitCommitId = "";
        var changeSetItems = buildDetails.getAllChangeSetItems();
        if (changeSetItems != null && !changeSetItems.isEmpty()) {
            var lastItem = changeSetItems.get(changeSetItems.size() - 1);
            gitCommitId = lastItem.getCommitId() != null ? lastItem.getCommitId() : "";
        }
        output.setGitCommitId(gitCommitId);

        // 制品URL（服务层已拼接）
        output.setArtifactUrl(buildDetails.getArtifactUrl() != null ? buildDetails.getArtifactUrl() : "");

        // 节点状态：Jenkins 构建非 SUCCESS 时，节点标记为失败（业务失败，可通过 continueOnFailure 控制）
        if (buildStatus == JenkinsBuildStatus.SUCCESS) {
            output.setStatus(NodeExecutionStatusEnum.SUCCESS);
            output.setMessage("Jenkins 构建成功");
        } else {
            output.setStatus(NodeExecutionStatusEnum.FAILURE);
            output.setMessage(String.format("Jenkins %s", buildStatus.getDescription()));
        }
    }
}
