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

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.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.JenkinsBuildResponse;
import com.qqchen.deploy.backend.deploy.integration.response.JenkinsConsoleOutputResponse;
import com.qqchen.deploy.backend.deploy.integration.response.JenkinsJobResponse;
import com.qqchen.deploy.backend.deploy.integration.response.JenkinsQueueBuildInfoResponse;
import com.qqchen.deploy.backend.deploy.integration.response.JenkinsCrumbIssuerResponse;
import com.qqchen.deploy.backend.deploy.integration.response.JenkinsViewResponse;
import com.qqchen.deploy.backend.deploy.repository.IExternalSystemRepository;
import com.qqchen.deploy.backend.system.enums.ExternalSystemTypeEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import org.apache.commons.lang3.StringUtils;

import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.xml.sax.InputSource;

import java.nio.charset.Charset;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Service
public class JenkinsServiceIntegrationImpl extends BaseExternalSystemIntegration implements IJenkinsServiceIntegration {

    @Resource
    private IExternalSystemRepository systemRepository;

    private final RestTemplate restTemplate = new RestTemplate();

    // Jenkins Crumb缓存 - 线程安全
    private static final Map<Long, JenkinsCrumbCache> CRUMB_CACHE = new ConcurrentHashMap<>();

    private static final long CRUMB_EXPIRE_TIME = 25 * 60 * 1000; // 25分钟过期

    /**
     * Jenkins Crumb缓存内部类
     */
    private static class JenkinsCrumbCache {
        final JenkinsCrumbIssuerResponse crumb;

        final ExternalSystem decryptedSystem;

        final long expireTime;

        JenkinsCrumbCache(JenkinsCrumbIssuerResponse crumb, ExternalSystem system) {
            this.crumb = crumb;
            this.decryptedSystem = system;
            this.expireTime = System.currentTimeMillis() + CRUMB_EXPIRE_TIME;
        }

        boolean isExpired() {
            return System.currentTimeMillis() > expireTime;
        }
    }

    /**
     * 线程安全地获取Jenkins Crumb缓存
     * 如果缓存不存在或已过期，会重新获取和解密
     */
    private synchronized JenkinsCrumbCache getCrumbCache(ExternalSystem system) {
        Long systemId = system.getId();
        JenkinsCrumbCache cache = CRUMB_CACHE.get(systemId);

        if (cache == null || cache.isExpired()) {
            log.debug("Jenkins Crumb缓存失效，重新获取: systemId={}", systemId);

            // 解密系统信息（只在这里解密一次）
            ExternalSystem decryptedSystem = decryptSystem(system);

            // 获取新的crumb
            JenkinsCrumbIssuerResponse crumb = fetchCrumbFromJenkins(decryptedSystem);

            // 创建新缓存
            cache = new JenkinsCrumbCache(crumb, decryptedSystem);
            CRUMB_CACHE.put(systemId, cache);

            log.debug("Jenkins Crumb缓存已更新: systemId={}, expireTime={}", systemId, cache.expireTime);
        }

        return cache;
    }

    /**
     * 直接从Jenkins服务器获取Crumb（不经过缓存）
     */
    private JenkinsCrumbIssuerResponse fetchCrumbFromJenkins(ExternalSystem decryptedSystem) {
        String url = decryptedSystem.getUrl() + "/crumbIssuer/api/json";
        HttpHeaders headers = createBasicHeaders(decryptedSystem);
        HttpEntity<String> entity = new HttpEntity<>(headers);

        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
        return convertResponse(response);
    }

    @Override
    public boolean testConnection(ExternalSystem system) {
        try {
            // 直接使用原始系统信息构建URL（URL不需要解密）
            String url = system.getUrl() + "/api/json";

            // 创建请求头（内部自动处理解密和crumb）
            HttpHeaders headers = createHeaders(system);

            // 打印实际发送的请求头
            log.info("Authorization头: {}", headers.getFirst("Authorization"));
            log.info("===================================");

            HttpEntity<String> entity = new HttpEntity<>(headers);

            ResponseEntity<String> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                entity,
                String.class
            );

            return response.getStatusCode() == HttpStatus.OK;
        } catch (Exception e) {
            log.error("Failed to connect to Jenkins: {}", system.getUrl(), e);
            return false;
        }
    }

    /**
     * 创建包含认证信息和Jenkins Crumb的完整请求头
     * 内部自动处理缓存获取和解密
     */
    private HttpHeaders createHeaders(ExternalSystem system) {
        // 获取缓存的解密系统和crumb
        JenkinsCrumbCache cache = getCrumbCache(system);
        ExternalSystem decryptedSystem = cache.decryptedSystem;
        JenkinsCrumbIssuerResponse crumb = cache.crumb;

        // 创建基础认证头
        HttpHeaders headers = createBasicHeaders(decryptedSystem);

        // 自动添加Jenkins Crumb和Cookie（如果存在）
        if (crumb != null) {
            if (crumb.getCrumb() != null) {
                headers.set("Jenkins-Crumb", crumb.getCrumb());
            }
            if (crumb.getCookie() != null) {
                headers.set("Cookie", crumb.getCookie());
            }
        }

        return headers;
    }

    /**
     * 创建基础认证头（不包含crumb），用于获取crumb时避免循环依赖
     */
    private HttpHeaders createBasicHeaders(ExternalSystem decryptedSystem) {
        HttpHeaders headers = new HttpHeaders();
        switch (decryptedSystem.getAuthType()) {
            case BASIC -> {
                String auth = decryptedSystem.getUsername() + ":" + decryptedSystem.getPassword();
                byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes());
                headers.set("Authorization", "Basic " + new String(encodedAuth));
            }
            case TOKEN -> {
                // Jenkins API Token认证也使用Basic Auth格式：username:apiToken
                String auth = decryptedSystem.getUsername() + ":" + decryptedSystem.getToken();
                byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes());
                headers.set("Authorization", "Basic " + new String(encodedAuth));
            }
            default -> throw new BusinessException(ResponseCode.JENKINS_AUTH_FAILED, new Object[] {decryptedSystem.getAuthType()});
        }

        // 设置接受JSON响应
        headers.set("Accept", "application/json");
        return headers;
    }


    @Override
    public String buildWithParameters(ExternalSystem externalSystem, String jobName, Map<String, String> parameters) {
        try {
            // 如果参数中包含PIPELINE_SCRIPT，确保Job配置中有该参数
            if (parameters.containsKey("PIPELINE_SCRIPT")) {
                ensurePipelineScriptParameter(externalSystem, jobName);
            }

            // 直接使用原始系统信息构建URL（URL不需要解密）
            String url = UriComponentsBuilder.fromHttpUrl(externalSystem.getUrl())
                .path("/job/")
                .path(jobName)
                .path("/buildWithParameters")
                .build()
                .toUriString();

            // 创建请求头（自动包含认证和crumb）
            HttpHeaders headers = createHeaders(externalSystem);
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

            // 4. 构建请求体
            MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
            parameters.forEach(formData::add);

            // 5. 发送请求
            HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(formData, headers);
            ResponseEntity<String> response = restTemplate.exchange(
                url,
                HttpMethod.POST,
                entity,
                String.class
            );

            // 6. 获取队列ID
            String location = response.getHeaders().getFirst("Location");
            if (location == null) {
                throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {201, "未获取到构建队列信息"});
            }
            return extractQueueId(location);
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("Failed to trigger Jenkins build: job={}, error={}", jobName, e.getMessage(), e);
            throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {"POST", e.getMessage()});
        }
    }

    /**
     * 确保Jenkins Job配置中包含PIPELINE_SCRIPT参数
     */
    private void ensurePipelineScriptParameter(ExternalSystem externalSystem, String jobName) {
        try {
            // 直接使用原始系统信息构建Job配置URL（URL不需要解密）
            String configUrl = String.format("%s/job/%s/config.xml", externalSystem.getUrl(), jobName);

            // 创建请求头（自动包含认证和crumb）
            HttpHeaders headers = createHeaders(externalSystem);
            headers.set(HttpHeaders.ACCEPT_CHARSET, "UTF-8");
            headers.set(HttpHeaders.CONTENT_TYPE, "application/xml;charset=UTF-8");
            headers.setAcceptCharset(Collections.singletonList(Charset.forName("UTF-8")));

            HttpEntity<String> entity = new HttpEntity<>(headers);

            // 配置RestTemplate，确保使用UTF-8编码
            restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));

            ResponseEntity<String> response = restTemplate.exchange(
                configUrl,
                HttpMethod.GET,
                entity,
                String.class
            );

            if (response.getBody() == null) {
                throw new BusinessException(ResponseCode.JENKINS_JOB_NOT_FOUND, new Object[] {jobName});
            }

            // 解析XML
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
            factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            DocumentBuilder builder = factory.newDocumentBuilder();

            // 使用UTF-8编码解析XML
            InputSource is = new InputSource(new StringReader(response.getBody()));
            is.setEncoding("UTF-8");
            Document doc = builder.parse(is);

            // 检查是否已存在PIPELINE_SCRIPT参数
            NodeList parameterDefinitions = doc.getElementsByTagName("parameterDefinitions");
            if (parameterDefinitions.getLength() > 0) {
                NodeList parameters = parameterDefinitions.item(0).getChildNodes();
                for (int i = 0; i < parameters.getLength(); i++) {
                    Node parameter = parameters.item(i);
                    if (parameter.getNodeType() == Node.ELEMENT_NODE) {
                        NodeList parameterChildren = parameter.getChildNodes();
                        for (int j = 0; j < parameterChildren.getLength(); j++) {
                            Node child = parameterChildren.item(j);
                            if ("name".equals(child.getNodeName()) && "PIPELINE_SCRIPT".equals(child.getTextContent())) {
                                // 参数已存在，无需添加
                                return;
                            }
                        }
                    }
                }
            }

            // 需要添加PIPELINE_SCRIPT参数
            Element root = doc.getDocumentElement();

            // 获取或创建properties元素
            NodeList propertiesList = doc.getElementsByTagName("properties");
            Element properties;
            if (propertiesList.getLength() == 0) {
                properties = doc.createElement("properties");
                root.appendChild(properties);
            } else {
                properties = (Element) propertiesList.item(0);
            }

            // 获取或创建ParametersDefinitionProperty元素
            NodeList paramDefProps = properties.getElementsByTagName("hudson.model.ParametersDefinitionProperty");
            Element paramDefProp;
            if (paramDefProps.getLength() == 0) {
                paramDefProp = doc.createElement("hudson.model.ParametersDefinitionProperty");
                properties.appendChild(paramDefProp);
            } else {
                paramDefProp = (Element) paramDefProps.item(0);
            }

            // 获取或创建parameterDefinitions元素
            NodeList paramDefs = paramDefProp.getElementsByTagName("parameterDefinitions");
            Element parameterDefs;
            if (paramDefs.getLength() == 0) {
                parameterDefs = doc.createElement("parameterDefinitions");
                paramDefProp.appendChild(parameterDefs);
            } else {
                parameterDefs = (Element) paramDefs.item(0);
            }

            // 创建PIPELINE_SCRIPT参数定义
            Element stringParam = doc.createElement("hudson.model.StringParameterDefinition");

            Element name = doc.createElement("name");
            name.setTextContent("PIPELINE_SCRIPT");
            stringParam.appendChild(name);

            Element description = doc.createElement("description");
            description.setTextContent("Pipeline脚本内容");
            stringParam.appendChild(description);

            Element defaultValue = doc.createElement("defaultValue");
            defaultValue.setTextContent("");
            stringParam.appendChild(defaultValue);

            Element trim = doc.createElement("trim");
            trim.setTextContent("false");
            stringParam.appendChild(trim);

            parameterDefs.appendChild(stringParam);

            // 更新Job配置时设置正确的编码
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();
            transformer.setOutputProperty(javax.xml.transform.OutputKeys.ENCODING, "UTF-8");
            transformer.setOutputProperty(javax.xml.transform.OutputKeys.VERSION, "1.1");
            transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(javax.xml.transform.OutputKeys.STANDALONE, "no");
            StringWriter writer = new StringWriter();
            transformer.transform(new DOMSource(doc), new StreamResult(writer));
            String updatedConfig = writer.toString();

            // 更新Job配置
            HttpHeaders updateHeaders = createHeaders(externalSystem);
            updateHeaders.setContentType(MediaType.APPLICATION_XML);
            updateHeaders.set(HttpHeaders.ACCEPT_CHARSET, "UTF-8");
            updateHeaders.set("Content-Type", "application/xml; charset=utf-8");
            HttpEntity<String> updateEntity = new HttpEntity<>(updatedConfig, updateHeaders);
            restTemplate.exchange(
                configUrl,
                HttpMethod.POST,
                updateEntity,
                String.class
            );

            log.info("Successfully added PIPELINE_SCRIPT parameter to Jenkins job: {}", jobName);
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("Failed to ensure PIPELINE_SCRIPT parameter for Jenkins job: {}, error: {}", jobName, e.getMessage(), e);
            throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {"UPDATE_CONFIG", e.getMessage()});
        }
    }

    /**
     * 清理XML内容，移除无效字符
     */
    private String cleanXmlContent(String xml) {
        if (xml == null) {
            return "";
        }

        // 移除XML中的无效字符
        StringBuilder cleanXml = new StringBuilder();
        for (int i = 0; i < xml.length(); i++) {
            char c = xml.charAt(i);
            // XML 1.1规范允许的字符范围（比1.0更宽松）
            if ((c == 0x9) ||
                (c == 0xA) ||
                (c == 0xD) ||
                ((c >= 0x20) && (c <= 0xD7FF)) ||
                ((c >= 0xE000) && (c <= 0xFFFD)) ||
                ((c >= 0x10000) && (c <= 0x10FFFF)) ||
                // 允许一些常见的控制字符
                (c >= 0x1) && (c <= 0x1F)) {
                cleanXml.append(c);
            }
        }
        return cleanXml.toString();
    }

    @Override
    public JenkinsQueueBuildInfoResponse getQueuedBuildInfo(ExternalSystem externalSystem, String queueId) {
        // 直接使用原始系统信息构建URL（URL不需要解密）
        String queueUrl = String.format("%s/queue/item/%s/api/json", externalSystem.getUrl().trim(), queueId);

        // 创建请求头（内部自动处理解密和crumb）
        HttpHeaders headers = createHeaders(externalSystem);
        HttpEntity<String> entity = new HttpEntity<>(headers);

        ResponseEntity<Map<String, Object>> response = restTemplate.exchange(queueUrl, HttpMethod.GET, entity, new ParameterizedTypeReference<>() {
        });

        Map<String, Object> queueInfo = response.getBody();
        if (queueInfo == null) {
            throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {200, "队列信息为空"});
        }

        // 检查是否已开始执行
        // 注意：即使 cancelled=true，Jenkins 也可能已分配 buildNumber
        // 此时应继续获取 buildNumber，让后续 pollBuildStatus 检测到 ABORTED 状态
        Map<String, Object> executable = (Map<String, Object>) queueInfo.get("executable");
        if (executable != null) {
            return new JenkinsQueueBuildInfoResponse(
                ((Number) executable.get("number")).intValue(),
                (String) executable.get("url")
            );
        }
        
        // 还在队列中等待
        return null;
    }


    @Override
    public JenkinsBuildStatus getBuildStatus(ExternalSystem externalSystem, String jobName, Integer buildNumber) {
        try {
            // 直接使用原始系统信息构建URL（URL不需要解密）
            String url = String.format("%s/job/%s/%d/api/json", externalSystem.getUrl().trim(), jobName, buildNumber);

            // 创建请求头（内部自动处理解密和crumb）
            HttpHeaders headers = createHeaders(externalSystem);
            HttpEntity<String> entity = new HttpEntity<>(headers);

            ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                entity,
                new ParameterizedTypeReference<>() {
                }
            );
            if (response.getBody() == null) {
                return JenkinsBuildStatus.NOT_FOUND;
            }
            String result = (String) response.getBody().get("result");
            if (result == null) {
                return JenkinsBuildStatus.IN_PROGRESS;
            }
            return switch (result) {
                case "SUCCESS" -> JenkinsBuildStatus.SUCCESS;
                case "FAILURE" -> JenkinsBuildStatus.FAILURE;
                case "ABORTED" -> JenkinsBuildStatus.ABORTED;
                default -> JenkinsBuildStatus.IN_PROGRESS;
            };
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("Failed to get build status: job={}, buildNumber={}", jobName, buildNumber, e);
            throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {"GET_STATUS", e.getMessage()});
        }
    }

    @Override
    public JenkinsBuildResponse getBuildDetails(ExternalSystem externalSystem, String jobName, Integer buildNumber) {
        try {
            // 不使用 tree 参数，直接获取完整的 build 详情（与老项目保持一致）
            // 原因：使用 tree 参数时，如果构建没有代码变更，Jenkins 不会返回 changeSets 字段
            String url = UriComponentsBuilder.fromHttpUrl(externalSystem.getUrl())
                .path("/job/")
                .path(jobName)
                .path("/")
                .path(buildNumber.toString())
                .path("/api/json")
                .build()
                .toUriString();

            // 创建请求头（自动包含认证和crumb）
            HttpHeaders headers = createHeaders(externalSystem);
            HttpEntity<String> entity = new HttpEntity<>(headers);
            ResponseEntity<String> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                entity,
                String.class
            );

            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
                ObjectMapper mapper = new ObjectMapper();
                JenkinsBuildResponse buildResponse = mapper.readValue(response.getBody(), JenkinsBuildResponse.class);

                // 清理URL，移除基础URL部分
                String baseUrl = StringUtils.removeEnd(externalSystem.getUrl(), "/");
                if (buildResponse.getUrl() != null) {
                    buildResponse.setUrl(buildResponse.getUrl().replace(baseUrl, ""));
                }

                // 计算结束时间（毫秒与格式化），当 timestamp 与 duration 均存在
                if (buildResponse.getTimestamp() != null && buildResponse.getDuration() != null) {
                    long endMillis = buildResponse.getTimestamp() + buildResponse.getDuration();
                    buildResponse.setEndTimeMillis(endMillis);
                    try {
                        buildResponse.setEndTime(DateUtils.formatMillis(endMillis));
                    } catch (Exception ignore) {
                        // 忽略格式化异常，保留 endTimeMillis
                    }
                }

                // 拼接制品完整URL串（逗号分隔）
                try {
                    if (buildResponse.getArtifacts() != null && !buildResponse.getArtifacts().isEmpty()) {
                        String buildPath = buildResponse.getUrl() != null ? buildResponse.getUrl() : (
                            "/job/" + jobName + "/" + buildNumber + "/");
                        String buildPageUrl = baseUrl + (buildPath.startsWith("/") ? buildPath : "/" + buildPath);
                        if (!buildPageUrl.endsWith("/")) {
                            buildPageUrl = buildPageUrl + "/";
                        }
                        final String artifactBaseUrl = buildPageUrl;
                        String joined = buildResponse.getArtifacts().stream()
                            .map(a -> artifactBaseUrl + "artifact/" + a.getRelativePath())
                            .collect(java.util.stream.Collectors.joining(","));
                        buildResponse.setArtifactUrl(joined);
                    } else {
                        buildResponse.setArtifactUrl("");
                    }
                } catch (Exception ex) {
                    log.warn("Failed to build artifact urls: {}", ex.getMessage());
                    buildResponse.setArtifactUrl("");
                }

                return buildResponse;
            }

            throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {200, "构建详情响应为空"});
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("Failed to get build details: job={}, buildNumber={}, error={}",
                jobName, buildNumber, e.getMessage(), e);
            throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {"GET_BUILD_DETAILS", e.getMessage()});
        }
    }

    private String extractQueueId(String location) {
        // location格式: http://jenkins-url/queue/item/12345/
        return location.replaceAll(".*/item/(\\d+)/.*", "$1");
    }

    public JenkinsCrumbIssuerResponse convertResponse(ResponseEntity<String> response) {
        // 1. 从响应体中解析JSON
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode;
        try {
            jsonNode = objectMapper.readTree(response.getBody());
        } catch (JsonProcessingException e) {
            throw new BusinessException(ResponseCode.JENKINS_RESPONSE_PARSE_ERROR, new Object[] {e.getMessage()});
        }

        // 2. 从响应头中获取cookie
        String cookie = response.getHeaders().getFirst(HttpHeaders.SET_COOKIE);  // 获取SET_COOKIE头的第一个值

        // 3. 构建返回对象
        JenkinsCrumbIssuerResponse result = new JenkinsCrumbIssuerResponse();
        result.setCrumb(jsonNode.get("crumb").asText());
        result.setCookie(cookie);

        return result;
    }

    /**
     * 通用的Jenkins API调用方法
     *
     * @param externalSystem Jenkins系统配置
     * @param path           API路径
     * @param treeQuery      tree查询参数
     * @param jsonArrayKey   JSON数组的key
     * @param responseType   响应类型的Class对象，用于类型安全
     * @param <T>            响应类型泛型
     * @return API响应结果
     */
    private <T> List<T> callJenkinsApi(ExternalSystem externalSystem,
                                       String path,
                                       String treeQuery,
                                       String jsonArrayKey,
                                       Class<T> responseType) {
        try {
            String url = UriComponentsBuilder.fromHttpUrl(externalSystem.getUrl())
                .path(path)
                .queryParam("tree", treeQuery)
                .build()
                .toUriString();

            HttpEntity<String> entity = new HttpEntity<>(createHeaders(externalSystem));
            ResponseEntity<String> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                entity,
                String.class
            );

            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
                ObjectMapper mapper = new ObjectMapper();
                JsonNode root = mapper.readTree(response.getBody());
                // 使用responseType进行类型安全的转换
                return mapper.convertValue(root.get(jsonArrayKey),
                    mapper.getTypeFactory().constructCollectionType(List.class, responseType));
            }
            return Collections.emptyList();
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("Failed to call Jenkins API: path={}, error={}, responseType={}",
                path, e.getMessage(), responseType.getSimpleName(), e);
            throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {path, e.getMessage()});
        }
    }

    /**
     * 查询所有视图
     *
     * @param externalSystem Jenkins系统配置
     * @return 视图列表
     */
    public List<JenkinsViewResponse> listViews(ExternalSystem externalSystem) {
        String treeQuery = "views[name,url,description,_class]";
        List<JenkinsViewResponse> views = callJenkinsApi(
            externalSystem,
            "/api/json",
            treeQuery,
            "views",
            JenkinsViewResponse.class
        );

        // 过滤和清洗数据
        final String baseUrl = StringUtils.removeEnd(externalSystem.getUrl(), "/");
        return views.stream()
            .filter(view -> !"all".equalsIgnoreCase(view.getName()))
            .peek(view -> {
                // 清理描述中的多余空格
                if (view.getDescription() != null) {
                    view.setDescription(view.getDescription().trim());
                }
                // 转换URL为相对路径
                if (view.getUrl() != null) {
                    view.setUrl(view.getUrl().replace(baseUrl, ""));
                }
            })
            .toList();
    }

    /**
     * 查询视图下的所有任务
     *
     * @param externalSystem Jenkins系统配置
     * @param viewName       视图名称
     * @return 任务列表
     */
    public List<JenkinsJobResponse> listJobs(ExternalSystem externalSystem, String viewName) {
        // 只查询必要的字段，确保包含lastBuild的timestamp字段
        String treeQuery = "jobs[name,url,description,buildable,nextBuildNumber," +
            "lastBuild[number,timestamp,result],color]";

        List<JenkinsJobResponse> jobs = callJenkinsApi(
            externalSystem,
            "/view/" + viewName + "/api/json",
            treeQuery,
            "jobs",
            JenkinsJobResponse.class
        );

        String baseUrl = StringUtils.removeEnd(externalSystem.getUrl(), "/");
        return jobs.stream()
            .peek(job -> {
                if (job.getDescription() != null) {
                    job.setDescription(job.getDescription().trim());
                }
                if (job.getUrl() != null) {
                    job.setUrl(job.getUrl().replace(baseUrl, ""));
                }
                if (job.getBuildable() == null) {
                    job.setBuildable(false);
                }
                if (job.getNextBuildNumber() == null) {
                    job.setNextBuildNumber(1);
                }
            })
            .toList();
    }

    @Override
    public JenkinsJobResponse job(ExternalSystem externalSystem, String jobName) {
        try {
            // 直接使用原始系统信息构建URL（URL不需要解密）
            String url = UriComponentsBuilder.fromHttpUrl(externalSystem.getUrl())
                .path("/job/")
                .path(jobName)
                .path("/api/json")
                .queryParam("tree", "name,url,description,buildable,nextBuildNumber,lastBuild[number,timestamp,result],color,healthReport[score,description]")
                .build()
                .toUriString();

            // 2. 发送请求
            HttpEntity<String> entity = new HttpEntity<>(createHeaders(externalSystem));
            ResponseEntity<String> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                entity,
                String.class
            );

            // 3. 解析响应
            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
                ObjectMapper mapper = new ObjectMapper();
                JenkinsJobResponse jobResponse = mapper.readValue(response.getBody(), JenkinsJobResponse.class);

                // 4. 清理URL，移除基础URL部分
                if (jobResponse.getUrl() != null) {
                    String baseUrl = StringUtils.removeEnd(externalSystem.getUrl(), "/");
                    jobResponse.setUrl(jobResponse.getUrl().replace(baseUrl, ""));
                }

                return jobResponse;
            }

            return null;
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("Failed to get Jenkins job: jobName={}, error={}", jobName, e.getMessage(), e);
            throw new BusinessException(ResponseCode.JENKINS_JOB_NOT_FOUND, new Object[] {jobName});
        }
    }

    /**
     * 查询任务的构建信息
     *
     * @param externalSystem Jenkins系统配置
     * @param jobName        任务名称
     * @return 构建信息列表
     */
    public List<JenkinsBuildResponse> listBuilds(ExternalSystem externalSystem, String jobName) {
        // Jenkins API 默认只返回最近的若干个构建，通过 {from,to} 可以指定范围
        String treeQuery = "builds[number,url,result,timestamp,duration,building,actions[_class,parameters[_class,name,value]]]";
        List<JenkinsBuildResponse> builds = callJenkinsApi(
            externalSystem,
            "/job/" + jobName + "/api/json",
            treeQuery,
            "builds",
            JenkinsBuildResponse.class
        );

        String baseUrl = StringUtils.removeEnd(externalSystem.getUrl(), "/");
        return builds.stream()
            .peek(build -> {
                if (build.getUrl() != null) {
                    build.setUrl(build.getUrl().replace(baseUrl, ""));
                }
            })
            .toList();
    }

    @Override
    public JenkinsConsoleOutputResponse getConsoleOutput(ExternalSystem externalSystem, String jobName, Integer buildNumber, Long startOffset) {
        try {
            if (startOffset == null) {
                startOffset = 0L;
            }

            // 直接使用原始系统信息构建 Jenkins Progressive Text API URL（URL不需要解密）
            String url = UriComponentsBuilder.fromHttpUrl(externalSystem.getUrl())
                .path("/job/")
                .path(jobName)
                .path("/")
                .path(buildNumber.toString())
                .path("/logText/progressiveText")
                .queryParam("start", startOffset)
                .build()
                .toUriString();

            // 创建请求头（自动包含认证和crumb）
            HttpHeaders headers = createHeaders(externalSystem);
            headers.setAccept(Collections.singletonList(MediaType.TEXT_PLAIN));
            HttpEntity<String> entity = new HttpEntity<>(headers);

            ResponseEntity<String> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                entity,
                String.class
            );

            // 解析响应
            JenkinsConsoleOutputResponse result = new JenkinsConsoleOutputResponse();

            // 从响应体获取日志内容（按行分割）
            String logContent = response.getBody();
            if (logContent != null && !logContent.isEmpty()) {
                List<String> lines = java.util.Arrays.asList(logContent.split("\n"));
                result.setLines(lines);
            } else {
                result.setLines(Collections.emptyList());
            }

            // 从响应头获取下一次读取的偏移量
            // Jenkins 返回 X-Text-Size 表示当前日志总大小
            // X-More-Data 表示是否还有更多数据
            String textSize = response.getHeaders().getFirst("X-Text-Size");
            String moreData = response.getHeaders().getFirst("X-More-Data");

            result.setNextOffset(textSize != null ? Long.parseLong(textSize) : startOffset);
            result.setHasMoreData("true".equalsIgnoreCase(moreData));
            result.setIsComplete(!"true".equalsIgnoreCase(moreData));

            log.debug("Retrieved Jenkins console output: job={}, build={}, offset={}->{}, lines={}, hasMore={}",
                jobName, buildNumber, startOffset, result.getNextOffset(),
                result.getLines().size(), result.getHasMoreData());

            return result;

        } catch (Exception e) {
            log.error("Failed to get Jenkins console output: job={}, build={}, offset={}, error={}", jobName, buildNumber, startOffset, e.getMessage(), e);

            // 返回空结果而不是抛异常，避免中断轮询
            JenkinsConsoleOutputResponse emptyResult = new JenkinsConsoleOutputResponse();
            emptyResult.setLines(Collections.emptyList());
            emptyResult.setNextOffset(startOffset);
            emptyResult.setHasMoreData(false);
            emptyResult.setIsComplete(false);
            return emptyResult;
        }
    }
}
