package com.arms.api.schedule.service;

import com.arms.api.schedule.model.dto.ScheduleInfoDTO;
import com.arms.api.schedule.model.vo.ScheduleDetailVO;
import com.arms.api.schedule.model.vo.ScheduleHistoryVO;
import com.arms.api.schedule.model.vo.ScheduleInfoVO;
import com.arms.api.schedule.model.vo.ScheduleMapVO;
import com.arms.api.schedule.util.ScheduleFileReader;
import com.arms.api.schedule.util.ScheduleMapProvider;
import com.arms.api.schedule.util.ScheduleTaskDispatcher;
import com.arms.api.systeminfo.model.SystemInfoVO;
import com.arms.api.systeminfo.service.SystemInfo;
import com.arms.api.util.GiteaFileUtil;
import com.arms.api.util.external_communicate.BackendCoreCommunicator;
import com.arms.api.util.external_communicate.dto.SearchDTO;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.arms.api.util.external_communicate.EngineCommunicator;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;



@RequiredArgsConstructor
@Slf4j
@Service("scheduleService")
public class ScheduleServiceImpl implements ScheduleService{

    private final ScheduleMapProvider mapProvider;
    private final ScheduleTaskDispatcher dispatcher;

    @Value("${spring.cloud.config.server.git.username}")
    private String gitUsername;
    @Value("${spring.cloud.config.server.git.password}")
    private String gitPassword;
    @Value("${spring.cloud.config.server.git.default-label}")
    private String gitBranch;
    @Value("${gitea.schedule-config}")
    private String scheduleDirectoryUrl;


    private final EngineCommunicator engineCommunicator;

    private final BackendCoreCommunicator backendCoreCommunicator;

    private final SystemInfo systemInfo;

    @Override
    public void almIssueMergeWithReindex() {
        try{

            SystemInfoVO systemInfoVO = systemInfo.getSystemInfo(0L);

            if(systemInfoVO!=null&&systemInfoVO.isExistLicenseNumber()){
                engineCommunicator.almIssueMergeWithReindex();
            }else{
                throw new IllegalStateException("라이센스 정보 가져오기 실패.");
            }

        }catch (Exception e){
            throw new IllegalStateException("라이센스 정보 가져오기 실패.");
        }

    }

    @Override
    public void fluentdMergeWithReindex() {
        try{

            SystemInfoVO systemInfoVO = systemInfo.getSystemInfo(0L);

            if(systemInfoVO!=null&&systemInfoVO.isExistLicenseNumber()){
                engineCommunicator.fluentdMergeWithReindex();
            }else{
                throw new IllegalStateException("라이센스 정보 가져오기 실패.");
            }

        }catch (Exception e){
            throw new IllegalStateException("라이센스 정보 가져오기 실패.");
        }

    }

    @Override
    public void almIssueMergeWithReindex(int day) {
        try{

            SystemInfoVO systemInfoVO = systemInfo.getSystemInfo(0L);

            if(systemInfoVO!=null&&systemInfoVO.isExistLicenseNumber()){
                engineCommunicator.almIssueMergeWithReindex(day);
            }else{
                throw new IllegalStateException("라이센스 정보 가져오기 실패.");
            }

        }catch (Exception e){
            throw new IllegalStateException("라이센스 정보 가져오기 실패.");
        }

    }

    @Override
    public void fluentdMergeWithReindex(int day) {
        try{

            SystemInfoVO systemInfoVO = systemInfo.getSystemInfo(0L);

            if(systemInfoVO!=null&&systemInfoVO.isExistLicenseNumber()){
                engineCommunicator.fluentdMergeWithReindex(day);
            }else{
                throw new IllegalStateException("라이센스 정보 가져오기 실패.");
            }

        }catch (Exception e){
            throw new IllegalStateException("라이센스 정보 가져오기 실패.");
        }

    }

    @Override
    public void serverInfoBackup() {
        engineCommunicator.serverInfoBackup();
    }

    @Override
    public void executeIncrementalIssueWithDateRangeSequentialSchedules(String startDate, String endDate) {
        backendCoreCommunicator.executeIncrementalIssueWithDateRangeSequentialSchedules(startDate,endDate);
    }

    @Override
    public void executeSequentialSchedules() {
        backendCoreCommunicator.executeSequentialSchedules();
    }

    @Override
    public void updateReqStatusFromElasticsearch() {
        backendCoreCommunicator.updateReqStatusFromElasticsearch();
    }

    @Override
    public ScheduleMapVO getScheduleList() throws Exception {
        ScheduleMapVO scheduleMapVO = new ScheduleMapVO();

        List<String> ymlFileFullLinks = GiteaFileUtil.getYamlFilesFromDirectory(scheduleDirectoryUrl, gitBranch, gitUsername, gitPassword);
        if (ymlFileFullLinks.isEmpty()) {
            log.info("[ ScheduleServiceImpl :: getScheduleList ] :: ymlFileFullLinks is empty");
            return new ScheduleMapVO();
        } else {
            for (String ymlFileFullLink : ymlFileFullLinks) {
                log.info("[ ScheduleServiceImpl :: getScheduleList ] :: ymlFileFullLink => {}", ymlFileFullLink);
                String ymlFileName = GiteaFileUtil.extractFileNameWithoutExtension(ymlFileFullLink);
                List<ScheduleInfoVO> scheduleInfoVOS = ScheduleFileReader.readScheduleYmlFileAndParseToVO(ymlFileFullLink, gitUsername, gitPassword);
                scheduleMapVO.getScheduleInfoMap().put(ymlFileName, scheduleInfoVOS);
            }
        }
        return scheduleMapVO;
    }

    @Override
    public List<ScheduleDetailVO> getCurrentScheduleList() {
        List<ScheduleDetailVO> result = new ArrayList<>();

        ScheduleMapVO scheduleMapVO = mapProvider.getScheduleMapVO();

        if (scheduleMapVO == null) {
            log.warn("[ ScheduleService :: getCurrentScheduleList ] :: scheduleMapVO is null");
            return Collections.emptyList();
        }
        if (scheduleMapVO.getScheduleInfoMap() == null) {
            log.warn("[ ScheduleService :: getCurrentScheduleList ] :: scheduleMapVO.getScheduleMap() is null");
            return Collections.emptyList();
        }

        Map<String, List<ScheduleInfoVO>> scheduleMap = scheduleMapVO.getScheduleInfoMap();

        scheduleMap.forEach( (fileName, list) ->
                {
                    for (ScheduleInfoVO vo : list) {
                        result.add(
                                ScheduleDetailVO.builder()
                                        .fileName(fileName)
                                        .name(vo.getName())
//                                        .methodName(vo.getMethodName())
                                        .cron(vo.getCron())
                                        .notes(vo.getNotes())
                                        .enabled(Boolean.TRUE.equals(vo.getEnabled()))
                                        .build()
                        );
                    }
                }
        );

        return result;
    }

    @SuppressWarnings("java:S2647")
    @Override
    public String saveScheduleList(String fileName, List<ScheduleInfoDTO> scheduleList) throws Exception {

        String fileNameWithExt = fileName + ".yml";

        URL url = new URL(scheduleDirectoryUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");

        // Set Basic Authentication
        String auth = gitUsername + ":" + gitPassword;
        String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
        connection.setRequestProperty("Authorization", "Basic " + encodedAuth);

        // 1. 커넥션 생성
        int responseCode = connection.getResponseCode();
        if (responseCode == HttpURLConnection.HTTP_OK) {

            try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
                StringBuilder response = new StringBuilder();
                String inputLine;
                while ((inputLine = in.readLine()) != null) {
                    response.append(inputLine);
                }

                ObjectMapper mapper = new ObjectMapper();
                JsonNode filesArray = mapper.readTree(response.toString());
                boolean fileExists = false;
                String fileSha = null;

                // 2. 파일이 저장소에 있는지 확인(파일명 활용)
                for (JsonNode file : filesArray) {
                    String name = file.get("name").asText();
                    if (fileNameWithExt.equals(name)) {
                        fileExists = true;
                        fileSha = file.get("sha").asText();  // 업데이트 시 필요
                        break;
                    }
                }

                String contents = ScheduleFileReader.convertScheduleListToYaml(scheduleList);

                // 3. 파일 존재 - 업데이트
                if (fileExists) {
                    log.info("File exists. Updating {}", fileNameWithExt);
                    int resultOfUpdate = updateFileInRepository(scheduleDirectoryUrl, fileNameWithExt, fileSha, gitBranch, contents);
                    return String.valueOf(resultOfUpdate);
                }
                // 파일이 존재하지 않음( 파실 신규 생성 고려, 신규 생성시는 201임)
                else {
                    log.error("File {} does not exists.", fileNameWithExt);
                    return "File does not exists.";
                }
            }

        } else {
            log.error("Failed to retrieve directory. ResponseCode: {}", responseCode);
            return String.valueOf(responseCode);
        }
    }

    @SuppressWarnings("java:S2647")
    private int updateFileInRepository(String apiUrl, String fileNameWithExt, String fileSha, String branch, String contents) throws Exception {

        URL url = new URL(apiUrl + fileNameWithExt);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("PUT");
        connection.setDoOutput(true);

        String auth = gitUsername + ":" + gitPassword;
        String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
        connection.setRequestProperty("Authorization", "Basic " + encodedAuth);
        connection.setRequestProperty("Content-Type", "application/json");

        // 업데이트할 파일 내용과 메타 정보 설정
        String content = Base64.getEncoder().encodeToString(contents.getBytes(StandardCharsets.UTF_8));
        String requestBody = String.format(
                "{\"content\":\"%s\",\"message\":\"Update %s\",\"sha\":\"%s\",\"branch\":\"%s\"}",
                content, fileNameWithExt, fileSha, branch
        );

        try (OutputStream os = connection.getOutputStream()) {
            os.write(requestBody.getBytes());
            os.flush();
        }

        int responseCode = connection.getResponseCode();
        if (responseCode == HttpURLConnection.HTTP_OK) {
            log.info("File {} updated successfully.", fileNameWithExt);
            return 200;
        } else {
            log.error("Failed to update file. Response code: {}", responseCode);
            return responseCode;
        }
    }

    @Override
    public List<ScheduleHistoryVO> getScheduleHistory(SearchDTO searchDTO) {
        return Optional.ofNullable(engineCommunicator.getScheduleHistory(searchDTO).getBody())
                .orElse(Collections.emptyList());
    }

    @Override
    public void retryFailedReqStatusCreationToElasticsearch() {
        backendCoreCommunicator.retryFailedReqStatusCreationToElasticsearch();
    }

    @Override
    public void cacheStatusMappingData() {
        backendCoreCommunicator.cacheStatusMappingData();
    }

    @Override
    public void updateArmsStateCategory() {
        engineCommunicator.updateArmsStateCategory();
    }

    @Override
    public void cloudJiraTestApiRequest() {
        engineCommunicator.cloudJiraTestApiRequest();
    }
}


