package cd.casic.ci.process.process.service.sast.impl;

import cd.casic.ci.process.dto.req.sast.*;
import cd.casic.ci.process.dto.resp.report.ReportResp;
import cd.casic.ci.process.dto.resp.sast.*;
import cd.casic.ci.process.process.dataObject.pipeline.PipPipeline;
import cd.casic.ci.process.process.dataObject.target.TargetVersion;
import cd.casic.ci.process.process.dataObject.volumnInfo.VulInfo;
import cd.casic.ci.process.process.service.pipeline.PipelineService;
import cd.casic.ci.process.process.service.sast.SastService;
import cd.casic.ci.process.process.service.target.TargetVersionService;
import cd.casic.ci.process.process.service.vulInfo.VulInfoService;
import cd.casic.ci.process.properties.SastProperties;
import cd.casic.framework.commons.exception.ServiceException;
import cd.casic.framework.commons.exception.enums.GlobalErrorCodeConstants;
import cd.casic.framework.commons.pojo.PageParam;
import cd.casic.framework.commons.pojo.PageResult;
import cd.casic.framework.redis.core.RedisTemplateUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import kotlin.text.Charsets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.luaj.vm2.ast.Str;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static cd.casic.ci.process.constant.SastUrlConstant.*;

@Service
@Slf4j
public class SastServiceImpl implements SastService {
    @Resource
    private RedisTemplateUtils redisTemplateUtils;
    @Resource
    private SastProperties sastProperties;
    @Resource
    private RestTemplate restTemplate;
    @Resource
    private PipelineService pipelineService;
    @Resource
    private TargetVersionService targetVersionService;
    @Resource
    private VulInfoService vulInfoService;
    public static final String TOKEN_PREFIX = "Bearer ";
    public static final String TOKEN_HEADER_KEY = "authorization";
    public static final String REDIS_SAST_TOKEN_KEY = "REDIS_SAST_TOKEN_KEY";


    private SastTokenResp getTokenRemote(){
        HttpHeaders httpHeaders = new HttpHeaders();
        HttpEntity<SastProperties> httpEntity = new HttpEntity<SastProperties>(sastProperties,httpHeaders);
        ResponseEntity<String> exchange = restTemplate.exchange(sastProperties.getBaseUrl() +getToken, HttpMethod.POST, httpEntity, String.class, new HashMap<>());
        String body = exchange.getBody();
        JSONObject bodyObject = JSON.parseObject(body);
        SastTokenResp tokenResp = new SastTokenResp();
        tokenResp.setAccessToken(bodyObject.getString("access_token"));
        tokenResp.setExpiresIn(bodyObject.getInteger("expires_in"));
        tokenResp.setRefreshExpiresIn(bodyObject.getInteger("refresh_expires_in"));
        tokenResp.setRefreshToken(bodyObject.getString("refresh_token"));
        tokenResp.setTokenType(bodyObject.getString("token_type"));
        return tokenResp;
    }

    @Override
    public String getToken() {
        Object o = redisTemplateUtils.get(REDIS_SAST_TOKEN_KEY);
        String token = o instanceof String ? ((String) o) : "";
        if (StringUtils.isEmpty(token)) {
            synchronized(this){
                o = redisTemplateUtils.get(REDIS_SAST_TOKEN_KEY);
                token = o instanceof String ? ((String) o) : "";
                if (StringUtils.isEmpty(token)) {
                    SastTokenResp tokenRemote = getTokenRemote();
                    String accessToken = tokenRemote.getAccessToken();
                    redisTemplateUtils.set(REDIS_SAST_TOKEN_KEY,accessToken,12*60*60);
                    token = accessToken;
                }
            }
        }
        return token;
    }

    @Override
    public Boolean applicationExist(String applicationName) {
        HttpHeaders headers = getHeaders();
        HttpEntity<Boolean> entity = new HttpEntity<>(null,headers);
        Map<String,Object> httpParams = new HashMap<>();
        String uriString = UriComponentsBuilder.fromHttpUrl(sastProperties.getBaseUrl() + applicationExist)
                .queryParam("applicationName", applicationName)
                .queryParam("applicationId", "").toUriString();
        ResponseEntity<Boolean> exchange = restTemplate.exchange(uriString, HttpMethod.GET, entity, Boolean.class, httpParams);
        return exchange.getBody();
    }

    @Override
    public SastFileUploadResp uploadFile(File multipartFile) {
        HttpHeaders httpHeaders = getHeaders();
        MultiValueMap<String, Object> entityMap = new LinkedMultiValueMap<>();
        httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
        FileSystemResource fileSystemResource = new FileSystemResource(multipartFile);
        entityMap.add("multipartFile",fileSystemResource);
        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(entityMap,httpHeaders);
        ResponseEntity<String> exchange = restTemplate.exchange(sastProperties.getBaseUrl() +fileUpload, HttpMethod.POST, httpEntity, String.class, new HashMap<>());
        return JSON.parseObject(exchange.getBody(), SastFileUploadResp.class);
    }

    @Override
    public SastIdentifiedLanguageTaskResp createIdentifiedLanguageTask(SastIdentifiedLanguageTaskReq req) {
        HttpHeaders httpHeaders = getHeaders();
        Map<String, List<String>> entityMap = new HashMap<>();
        entityMap.put("fileIds",req.getFileIds());
        HttpEntity<Map<String, List<String>>> entity = new HttpEntity<>(entityMap,httpHeaders);
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        ResponseEntity<SastIdentifiedLanguageTaskResp> exchange = restTemplate.exchange(sastProperties.getBaseUrl() +createIdentifiedLanguageTask, HttpMethod.POST,entity,SastIdentifiedLanguageTaskResp.class,new HashMap<>());
        return exchange.getBody();
    }
    @Override

    public SastIdentifiedLanguageTaskResp getIdentifiedLanguageTaskStatus(String taskId){
        HttpHeaders httpHeaders = getHeaders();
        Map<String, String> entityMap = new HashMap<>();
//        entityMap.put("taskId",taskId);
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(entityMap,httpHeaders);
//        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        String uriString = UriComponentsBuilder.fromHttpUrl(sastProperties.getBaseUrl()  + getIdentifiedLanguageTaskStatus).queryParam("taskId", taskId).toUriString();
        System.out.println(uriString);
        ResponseEntity<SastIdentifiedLanguageTaskResp> exchange = restTemplate.exchange(uriString, HttpMethod.GET,entity,SastIdentifiedLanguageTaskResp.class,new HashMap<>());
        return exchange.getBody();
    }
    @Override
    public List<SastEngineConfigResp> engineConfig(List<String> languageIdList){
        HttpHeaders httpHeaders = getHeaders();
        Map<String, String> entityMap = new HashMap<>();
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(entityMap,httpHeaders);
        String uriString = UriComponentsBuilder.fromHttpUrl(sastProperties.getBaseUrl()  + engineConfig).queryParam("languageIds", StringUtils.joinWith(",", languageIdList.toArray())).toUriString();
        // 别问我为啥要解码。坑爹的不支持Uri encode识别
        ResponseEntity<String> exchange = restTemplate.exchange(uriString, HttpMethod.GET,entity, String.class,new HashMap<>());
        return JSON.parseArray(exchange.getBody(), SastEngineConfigResp.class);
    }
    @Override
    public SastDetectionConfigResp detectionConfig(String languageId){
        HttpHeaders httpHeaders = getHeaders();
        Map<String, String> entityMap = new HashMap<>();
//        entityMap.put("taskId",taskId);
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(entityMap,httpHeaders);
        String uriString = UriComponentsBuilder.fromHttpUrl(sastProperties.getBaseUrl()  + detectionConfig).queryParam("languageId", languageId).toUriString();
        System.out.println(uriString);
        ResponseEntity<SastDetectionConfigResp> exchange = restTemplate.exchange(uriString, HttpMethod.GET,entity,SastDetectionConfigResp.class,new HashMap<>());
        return exchange.getBody();
    }

    @Override
    public SastApplicationCreateResp applicationCreate(SastApplicationCreateReq req) {
        HttpHeaders httpHeaders = getHeaders();
        HttpEntity<SastApplicationCreateReq> entity = new HttpEntity<>(req,httpHeaders);
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        ResponseEntity<SastApplicationCreateResp> exchange = restTemplate.exchange(sastProperties.getBaseUrl() +applicationCreate, HttpMethod.POST,entity,SastApplicationCreateResp.class,new HashMap<>());
        SastApplicationCreateResp body = exchange.getBody();
        return body;
    }
    @Override
    public SastApplicationCreateResp applicationStash(SastApplicationStashReq req) {
        HttpHeaders httpHeaders = getHeaders();
        HttpEntity<SastApplicationStashReq> entity = new HttpEntity<>(req,httpHeaders);
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        ResponseEntity<SastApplicationCreateResp> exchange = restTemplate.exchange(sastProperties.getBaseUrl() +applicationStash, HttpMethod.POST,entity,SastApplicationCreateResp.class,new HashMap<>());
        SastApplicationCreateResp body = exchange.getBody();
        return body;
    }
    @Override
    public SastApplicationEchoResp getApplicationEcho(String applicationId){
        HttpHeaders httpHeaders = getHeaders();
        Map<String, String> entityMap = new HashMap<>();
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(entityMap,httpHeaders);
        String uriString = UriComponentsBuilder.fromHttpUrl(sastProperties.getBaseUrl()  + getApplicationEcho).queryParam("applicationId", applicationId).toUriString();
        System.out.println(uriString);
        ResponseEntity<SastApplicationEchoResp> exchange = restTemplate.exchange(uriString, HttpMethod.GET,entity,SastApplicationEchoResp.class,new HashMap<>());
        SastApplicationEchoResp body = exchange.getBody();
        body.setApplicationId(applicationId);
        return body;
    }

    @Override
    public String reportIndex(SastReportCreateReq req) {
        HttpHeaders httpHeaders = getHeaders();
        HttpEntity<SastReportCreateReq> entity = new HttpEntity<>(req,httpHeaders);
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        ResponseEntity<JSONObject> exchange = restTemplate.exchange(sastProperties.getBaseUrl() +reportIndex, HttpMethod.POST,entity,JSONObject.class,new HashMap<>());
        JSONObject body = exchange.getBody();
        return body.getString("id");
    }
    @Override
    public String reportDownload(String reportId){
        HttpHeaders httpHeaders = getHeaders();
        Map<String, String> entityMap = new HashMap<>();
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(entityMap,httpHeaders);
        String uriString = UriComponentsBuilder.fromHttpUrl(sastProperties.getBaseUrl()  + reportDownload).queryParam("reportId", reportId).toUriString();
        System.out.println(uriString);
        ResponseEntity<String> exchange = restTemplate.exchange(uriString, HttpMethod.GET,entity, String.class,new HashMap<>());
        return exchange.getBody().replaceAll("\"","");
    }
    @Override
    public List<SastLanguageResp> language(){
        HttpHeaders httpHeaders = getHeaders();
        Map<String, String> entityMap = new HashMap<>();
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(entityMap,httpHeaders);
        String uriString = UriComponentsBuilder.fromHttpUrl(sastProperties.getBaseUrl()  + language).queryParam("codeSourceFrom", "CODE").toUriString();
        System.out.println(uriString);
        ResponseEntity<String> exchange = restTemplate.exchange(uriString, HttpMethod.GET,entity, String.class,new HashMap<>());
        return JSON.parseArray(exchange.getBody(),SastLanguageResp.class);
    }

    @Override
    public PageResult<SastReportResp> reportList(PageParam pageParam) {
        HttpHeaders httpHeaders = getHeaders();
        Map<String, String> entityMap = new HashMap<>();
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(entityMap,httpHeaders);
        String uriString = UriComponentsBuilder.fromHttpUrl(sastProperties.getBaseUrl()  + reportList)
                .queryParam("page", pageParam.getPageNo())
                .queryParam("size", pageParam.getPageSize())
                .toUriString();
        System.out.println(uriString);
        ResponseEntity<String> exchange = restTemplate.exchange(uriString, HttpMethod.GET,entity, String.class,new HashMap<>());
        JSONObject jsonObject = JSON.parseObject(exchange.getBody());
        List<SastReportResp> respList = JSONArray.parseArray(jsonObject.getString("content"), SastReportResp.class);

        PageResult<SastReportResp> pageResult = new PageResult<SastReportResp>(respList,jsonObject.getLong("totalElements"),Long.valueOf(String.valueOf(pageParam.getPageNo())),Long.valueOf(String.valueOf(pageParam.getPageSize())));
        return pageResult;
    }

    @Override
    public void reportDelete(String reportId){
        HttpHeaders httpHeaders = getHeaders();
        Map<String, String> entityMap = new HashMap<>();
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(entityMap,httpHeaders);
        String uriString = UriComponentsBuilder.fromHttpUrl(sastProperties.getBaseUrl()  + reportDelete).queryParam("id", reportId).toUriString();
        System.out.println(uriString);
        ResponseEntity<Void> exchange = restTemplate.exchange(uriString, HttpMethod.DELETE,entity, Void.class,new HashMap<>());
    }
    @Override
    public SastApplicationStatusResp getApplicationStatus(String applicationId){
        HttpHeaders httpHeaders = getHeaders();
        Map<String, String> entityMap = new HashMap<>();
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(entityMap,httpHeaders);
        String uriString=sastProperties.getBaseUrl()+getApplicationStatus+applicationId;
        ResponseEntity<SastApplicationStatusResp> exchange = restTemplate.exchange(uriString, HttpMethod.GET,entity, SastApplicationStatusResp.class,new HashMap<>());
        return exchange.getBody();
    }
    @Override
    public JSONObject jsonReportDownload(String reportId){
        HttpHeaders httpHeaders = getHeaders();
        Map<String, String> entityMap = new HashMap<>();
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(entityMap,httpHeaders);
        String uriString=UriComponentsBuilder.fromUriString(sastProperties.getBaseUrl()+jsonReportDownload)
                .queryParam("reportId",reportId).toUriString();
        log.info("sast 下载报告url:{}",uriString);
        ResponseEntity<String> exchange = restTemplate.exchange(uriString, HttpMethod.GET,entity, String.class,new HashMap<>());
        return JSON.parseObject(exchange.getBody());
    }

    @Override
    public SastReportStatusResp getReportStatus(String reportId) {
        HttpHeaders httpHeaders = getHeaders();
        Map<String, String> entityMap = new HashMap<>();
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(entityMap,httpHeaders);
        String uriString=UriComponentsBuilder.fromUriString(sastProperties.getBaseUrl()+getReportInfo)
                .queryParam("id",reportId).toUriString();
        log.info("sast 下载报告url:{}",uriString);
        ResponseEntity<SastReportStatusResp> exchange = restTemplate.exchange(uriString, HttpMethod.GET,entity, SastReportStatusResp.class,new HashMap<>());
        return exchange.getBody();
    }

    @Override
    public SastApplicationStashResp stashScan(String applicationId) {
        HttpHeaders httpHeaders = getHeaders();
        Map<String, String> entityMap = new HashMap<>();
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(entityMap,httpHeaders);
        String uriString=sastProperties.getBaseUrl()+stashScan+applicationId;
        log.info("sast 开始任务url:{}",uriString);
        ResponseEntity<SastApplicationStashResp> exchange = restTemplate.exchange(uriString, HttpMethod.POST,entity, SastApplicationStashResp.class,new HashMap<>());
        return exchange.getBody();
    }
    @Override
    public List<SastEngineLogResp> engineLog(String applicationId) {
        HttpHeaders httpHeaders = getHeaders();
        Map<String, String> entityMap = new HashMap<>();
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(entityMap,httpHeaders);
        String uriString=sastProperties.getBaseUrl()+engineLog;
        uriString=String.format(uriString,applicationId);
        log.info("sast 开始任务url:{}",uriString);
        ResponseEntity<String> exchange = restTemplate.exchange(uriString, HttpMethod.GET,entity, String.class,new HashMap<>());
        return JSONArray.parseArray(exchange.getBody(), SastEngineLogResp.class);
    }

    @Override
    public SastApplicationBinaryStashResp applicationBinaryStash(SastApplicationBinaryStashReq req) {
        HttpHeaders httpHeaders = getHeaders();
        req.setProjectId("893ed995-5b81-474a-96a9-2800281421cd");
        req.setApplicationName("二进制任务"+ UUID.randomUUID().toString());
        req.setCodeSourceFrom("BINARY");
        req.setArchAutoIdentify(true);
        String pipelineId = req.getPipelineId();
        PipPipeline pipeline = pipelineService.getById(pipelineId);
        String targetVersionId = pipeline.getTargetVersionId();
        TargetVersion targetVersion = targetVersionService.getById(targetVersionId);
        File targetFile=new File(targetVersion.getFilePath());
        SastFileUploadResp uploadResp = uploadFile(targetFile);
        String id = uploadResp.getId();
        req.setFileId(Collections.singletonList(id));
        log.info("sast二进制上传文件成功，开始调用接口{}",req);
        HttpEntity<SastApplicationBinaryStashReq> entity = new HttpEntity<>(req,httpHeaders);
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        ResponseEntity<SastApplicationBinaryStashResp> exchange = restTemplate.exchange(sastProperties.getBaseUrl() +applicationBinaryStash, HttpMethod.POST,entity,SastApplicationBinaryStashResp.class,new HashMap<>());
        return exchange.getBody();
    }
    @Override
    public SastApplicationStashResp binaryStashScan(String applicationId) {
        HttpHeaders httpHeaders = getHeaders();
        Map<String, String> entityMap = new HashMap<>();
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(entityMap,httpHeaders);
        String uriString=sastProperties.getBaseUrl()+binaryStashScan+applicationId;
        log.info("sast 开始任务url:{}",uriString);
        ResponseEntity<SastApplicationStashResp> exchange = restTemplate.exchange(uriString, HttpMethod.POST,entity, SastApplicationStashResp.class,new HashMap<>());
        return exchange.getBody();
    }

    @Override
    public void saveReportVulInfo(List<SastVulInfoReq> list,String targetType,String targetName,String city,String instanceId,String taskId,String taskType) {
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        List<VulInfo> vulInfos = new ArrayList<>(list.size());
        for (SastVulInfoReq req : list) {
            VulInfo vulInfo = new VulInfo();
            // 设置安全等级、名称
            vulInfo.setVulTitle(req.getName());
            String severity = req.getSeverity();
            List<SastVulInfoReq.DefectDetail> defectDetails = req.getDefectDetails();
            // 设置描述修复措施
            if (CollectionUtils.isEmpty(defectDetails)) {
                continue;
            }
            //设置流水线相关属性,因为漏洞描述还有漏洞修复建议列表里面每一项都是一样的，所以直接取第一个
            SastVulInfoReq.DefectDetail first = defectDetails.get(0);
            String description = first.getDescription();
            String potentialRisk = first.getPotentialRisk();
            vulInfo.setSolution(potentialRisk);
            vulInfo.setVulDescription(description);
            vulInfo.setSeverity(securityLevelToSeverity(severity));
            vulInfo.setTargetType(targetType);
            vulInfo.setTargetName(targetName);
            vulInfo.setCity(city);
            vulInfo.setInstanceId(instanceId);
            vulInfo.setTaskId(taskId);
            vulInfo.setTaskType(taskType);
            vulInfos.add(vulInfo);
        }
        vulInfoService.saveBatch(vulInfos);
    }

    private HttpHeaders getHeaders(){
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.set(TOKEN_HEADER_KEY,TOKEN_PREFIX+getToken());
        return httpHeaders;
    }
    private Integer securityLevelToSeverity(String securityLevel) {
        if (securityLevel == null) {
            return 0;
        }
        // 映射规则：严重 -> 1, 高危 -> 2, 中危 -> 3, 低危 -> 4
        return switch (securityLevel) {
            case "严重" -> 1;
            case "高危" -> 2;
            case "中危" -> 3;
            case "安全", "低危" -> 4;
            default -> 0;
        };
    }
}
