package com.arms.api.languageconfig.service;

import com.arms.api.languageconfig.model.vo.LanguagePackFileVO;
import com.arms.api.languageconfig.model.vo.SingleLanguagePackVO;
import com.arms.api.languageconfig.util.LanguagePackFileReader;
import com.arms.api.util.GiteaFileUtil;
import com.arms.config.GiteaUserConfig;
import com.arms.egovframework.javaservice.gcframework.model.FileContent;
import com.arms.egovframework.javaservice.gcframework.model.RepoType;
import com.arms.egovframework.javaservice.gcframework.model.GitFileInfo;
import com.arms.egovframework.javaservice.gcframework.service.GitFileService;
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 java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class LanguageConfigServiceImpl implements LanguageConfigService {

    private final GiteaUserConfig giteaUserConfig;
    private final GitFileService gitFileService;

    @Value("${gitea.language-config}")
    private String languageRepositoryUrl;
    @Value("${gitea.lang-pack.repo-name}")
    private String repoName;

    private Map<String, Map<String, Object>> languagePacks = new HashMap<>();
    private List<String> availableLanguages = new ArrayList<>();
    private LocalDateTime lastRefreshTime;

    @Override
    public List<LanguagePackFileVO> getLanguagePackFiles(RepoType repoType) {
        List<LanguagePackFileVO> fileList = new ArrayList<>();

        List<GitFileInfo> gitFileInfos = gitFileService.listFilesAndDirectories(repoType, repoName, "/");
        for (GitFileInfo fileInfo : gitFileInfos) {
            String fileName = fileInfo.getName();
            String languageCode = fileName.replace(".json", "");
            fileList.add(
                    LanguagePackFileVO.builder()
                            .fileName(fileName)
                            .languageCode(languageCode)
                            .filePath(fileInfo.getPath())
                            .sha(fileInfo.getSha() != null ? fileInfo.getSha() : "")
                            .size(fileInfo.getSize())
                            .build());
        }

        return fileList;
    }

    @Override
    public List<LanguagePackFileVO> getLanguagePackFiles_old() {
        List<LanguagePackFileVO> fileList = new ArrayList<>();

        try {
            // Gitea 에서 언어팩 파일 목록 조회 - list of file download url
            // 언어팩 파일 url 목록.
            List<String> languagePackFileUrls = GiteaFileUtil.getJsonFileUrlsInDirectory(
                            languageRepositoryUrl, giteaUserConfig.getGitBranch(), giteaUserConfig.getGitUsername(), giteaUserConfig.getGitPassword());

            for (String fileUrl : languagePackFileUrls) {
                String fileName = GiteaFileUtil.extractFileNameWithoutExtension(fileUrl);
                String languageCode = fileName.replace(".json", ""); // ko.json -> ko

                // 각 파일의 상세 정보 조회
                Map<String, Object> fileInfo =
                        LanguagePackFileReader.getFileInfoFromGitea(fileUrl, giteaUserConfig.getGitUsername(), giteaUserConfig.getGitPassword());

                if (fileInfo != null) {
                    // 해당 언어팩의 키 개수 계산
                    Map<String, Object> languagePack = languagePacks.get(languageCode);
                    int totalKeys = 0;
                    if (languagePack != null) {
                        Map<String, String> flattenedPack = LanguagePackFileReader.flattenLanguagePack(languagePack);
                        totalKeys = flattenedPack.size();
                    }

                    LanguagePackFileVO fileVO = LanguagePackFileVO.builder()
                            .fileName(fileName)
                            .languageCode(languageCode)
                            .filePath((String) fileInfo.get("path"))
                            .sha((String) fileInfo.get("sha"))
                            .size((Integer) fileInfo.get("size"))
                            .lastModified((String) fileInfo.get("updated_at"))
                            .totalKeys(totalKeys)
                            .build();

                    fileList.add(fileVO);
                }
            }
            return fileList;

        } catch (Exception e) {
            log.error("[LanguageConfigServiceImpl::getLanguagePackFiles] :: Error getting language pack files: {}", e.getMessage(), e);
            throw new RuntimeException("Failed to get language pack files", e);
        }
    }

    @Override
    public SingleLanguagePackVO getSingleLanguagePack(String language) {
        log.info("[LanguageConfigServiceImpl :: getSingleLanguagePack] :: language pack for: {}", language);

        try {
            // 언어팩 자체의 캐시용 콜렉션(languagePacks)에 있는지 확인.
            ensureLanguagePacksAvailable();

            if (!availableLanguages.contains(language)) {
                throw new RuntimeException("Language not found: " + language);
            }
            Map<String, Object> languagePack = languagePacks.get(language);

            if (languagePack == null || languagePack.isEmpty()) { // 캐시에 없을 경우 파일 직접 접근후 가져오기
                String fileUrl = languageRepositoryUrl + language + ".json?ref=main";
                try {
                    languagePack = LanguagePackFileReader.readLanguagePackFile(fileUrl, giteaUserConfig.getGitUsername(), giteaUserConfig.getGitPassword());
                    languagePacks.put(language, languagePack);
                } catch (Exception e) {
                    log.error("[LanguageConfigServiceImpl :: getSingleLanguagePack] :: Error getting language pack: {}", e.getMessage(), e);
                    throw new RuntimeException("Failed to get language pack", e);
                }
            }

            // 평면화된 형태로 변환
            Map<String, String> flattenedPack = LanguagePackFileReader.flattenLanguagePack(languagePack);

            return SingleLanguagePackVO.builder()
                    .language(language)
                    .languagePack(flattenedPack)
                    .totalKeys(flattenedPack.size())
                    .lastModified(lastRefreshTime != null ? lastRefreshTime.toString() : LocalDateTime.now().toString())
                    .build();
        } catch (Exception e) {
            log.error("Error getting single language pack: {}", e.getMessage(), e);
            throw new RuntimeException("Failed to get single language pack", e);
        }
    }

    @Override
    public SingleLanguagePackVO refreshSingleLanguagePack(String language) {
        languagePacks.remove(language);
        return getSingleLanguagePack(language);
    }

    @Override
    @SuppressWarnings("java:S2647")
    public String deleteSingleLanguagePack(String language) {
        log.info("[LanguageConfigServiceImpl :: deleteSingleLanguagePack] :: language pack for: {}", language);
        String fileName = language + ".json";
        String fileUrl = languageRepositoryUrl + fileName + "?ref=main";
        log.info("[LanguageConfigServiceImpl :: deleteSingleLanguagePack] :: fileUrl: {}", fileUrl);

        try {
            URL url = new URL(fileUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");

            // Basic Authentication 설정
            String auth = giteaUserConfig.getGitUsername() + ":" + giteaUserConfig.getGitPassword();
            String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
            connection.setRequestProperty("Authorization", "Basic " + encodedAuth);

            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();
                    log.debug("API Response: {}", response.toString());
                    JsonNode fileNode = mapper.readTree(response.toString());
                    boolean fileExists = false;
                    String fileSha = null;

                    // 파일이 저장소에 있는지 확인(파일명 활용)
                    if (fileNode.has("name")) {
                        String name = fileNode.get("name").asText();
                        log.debug("File name from response: {}", name);
                        if (fileName.equals(name)) {
                            fileExists = true;
                            if (fileNode.has("sha")) {
                                fileSha = fileNode.get("sha").asText();  // 삭제 시 필요
                                log.debug("File SHA: {}", fileSha);
                            } else {
                                log.warn("SHA not found in response for file: {}", fileName);
                                return "Error: SHA not found";
                            }
                        }
                    } else {
                        log.warn("Response does not contain 'name' field: {}", fileNode.toString());
                        return "Error: File name not found in response";
                    }

                    // 파일이 존재하면 삭제
                    if (fileExists) {
                        log.info("File exists. Deleting " + fileName);
                        int resultOfDelete = deleteFileInRepository(languageRepositoryUrl, fileName, fileSha, giteaUserConfig.getGitBranch());
                        languagePacks.remove(language);
                        return resultOfDelete == 200 ? fileName : String.valueOf(resultOfDelete);
                    } else {
                        log.warn("File does not exist: {}", fileName);
                        return "Error: File not found";
                    }
                } catch (Exception e) {
                    log.error("Error deleting language pack: {}", e.getMessage(), e);
                    throw new RuntimeException("Failed to delete language pack", e);
                } finally {
                    connection.disconnect();
                }
            } else if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
                log.warn("File does not exist (404): {}", fileName);
                return "Error: File not found (404)";
            } else {
                log.error("Failed to retrieve file. Response code: " + responseCode);
                return String.valueOf(responseCode);
            }
        } catch (Exception e) {
            log.error("Error deleting language pack: {}", e.getMessage(), e);
            throw new RuntimeException("Failed to delete language pack", e);
        }
    }

    @Override
    @SuppressWarnings("java:S2647")
    public String updateSingleLanguagePack(String language, Map<String, String> languagePack) throws IOException {
        log.info("[LanguageConfigServiceImpl :: updateSingleLanguagePack] :: language pack for: {}", language);
        String fileName = language + ".json";
        String fileUrl = languageRepositoryUrl + fileName + "?ref=main";
        log.info("[LanguageConfigServiceImpl :: updateSingleLanguagePack] :: fileUrl: {}", fileUrl);

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

        // Basic Authentication 설정
        String auth = giteaUserConfig.getGitUsername() + ":" + giteaUserConfig.getGitPassword();
        String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
        connection.setRequestProperty("Authorization", "Basic " + encodedAuth);

        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();
                log.debug("API Response: {}", response.toString());
                JsonNode fileNode = mapper.readTree(response.toString());
                boolean fileExists = false;
                String fileSha = null;

                // 1. 파일이 저장소에 있는지 확인(파일명 활용)
                if (fileNode.has("name")) {
                    String name = fileNode.get("name").asText();
                    log.debug("File name from response: {}", name);
                    if (fileName.equals(name)) {
                        fileExists = true;
                        if (fileNode.has("sha")) {
                            fileSha = fileNode.get("sha").asText();  // 업데이트 시 필요
                            log.debug("File SHA: {}", fileSha);
                        } else {
                            log.warn("SHA not found in response for file: {}", fileName);
                        }
                    }
                } else {
                    log.warn("Response does not contain 'name' field: {}", fileNode.toString());
                }

                // 2. LanguagePack un-flatten & parse to a String of JSON format
                Map<String, Object> stringObjectMap = LanguagePackFileReader.unflattenLanguagePack(languagePack);
                ObjectMapper objectMapper = new ObjectMapper();
                String contents = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(stringObjectMap);

                // 3-1. 파일이 존재하면 업데이트
                if (fileExists) {
                    log.info("File exists. Updating " + fileName);
                    int resultOfUpdate = updateFileInRepository(languageRepositoryUrl, fileName, fileSha, giteaUserConfig.getGitBranch(), contents);
                    return  resultOfUpdate == 200 ? fileName : String.valueOf(resultOfUpdate);
                }
                // 3-2. 파일이 없으면 신규 파일 생성
                else {
                    log.info("File does not exist. Creating " + fileName);
                    int resultOfCreation = createNewFileInRepository(languageRepositoryUrl, fileName, giteaUserConfig.getGitBranch(), contents);
                    return  resultOfCreation == 201 ? fileName : String.valueOf(resultOfCreation);
                }

            } catch (Exception e) {
                log.error("Error updating language pack: {}", e.getMessage(), e);
                throw new RuntimeException("Failed to update language pack", e);
            } finally {
                languagePacks.remove(language);
                connection.disconnect();
            }
        } else if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
            // 파일이 존재하지 않는 경우 (404), 새 파일 생성
            log.info("File does not exist (404). Creating new file: " + fileName);
            Map<String, Object> stringObjectMap = LanguagePackFileReader.unflattenLanguagePack(languagePack);
            ObjectMapper objectMapper = new ObjectMapper();
            String contents = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(stringObjectMap);

            int resultOfCreation = createNewFileInRepository(languageRepositoryUrl, fileName, giteaUserConfig.getGitBranch(), contents);
            return resultOfCreation == 201 ? fileName : String.valueOf(resultOfCreation);
        } else {
            log.error("Failed to retrieve directory. Response code: " + responseCode);
            return String.valueOf(responseCode);
        }
    }

    // 파일 업데이트 메서드
    @SuppressWarnings("java:S2647")
    private int updateFileInRepository(String apiUrl, String fileName, String fileSha, String branch, String contents) throws IOException {

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

        String auth = giteaUserConfig.getGitUsername() + ":" + giteaUserConfig.getGitPassword();
        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, fileName, 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 " + fileName + " updated successfully.");
            return 200;
        } else {
            log.error("Failed to update file. Response code: " + responseCode);
            return responseCode;
        }
    }

    // 신규 파일 생성 메서드
    @SuppressWarnings("java:S2647")
    private int createNewFileInRepository(String apiUrl, String fileName, String branch, String contents) throws IOException {

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

        String auth = giteaUserConfig.getGitUsername() + ":" + giteaUserConfig.getGitPassword();
        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\":\"Create %s\",\"branch\":\"%s\"}",
                content, fileName, branch
        );

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

        int responseCode = connection.getResponseCode();
        if (responseCode == HttpURLConnection.HTTP_CREATED) {
            log.info("File " + fileName + " created successfully.");
            return 201;
        } else {
            log.error("Failed to create file. Response code: " + responseCode);
            return responseCode;
        }
    }

    // 파일 삭제 메서드
    @SuppressWarnings("java:S2647")
    private int deleteFileInRepository(String apiUrl, String fileName, String fileSha, String branch) throws IOException {
        URL url = new URL(apiUrl + fileName);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("DELETE");
        connection.setDoOutput(true);

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

        // 삭제할 파일의 메타 정보 설정
        String requestBody = String.format(
                "{\"message\":\"Delete %s\",\"sha\":\"%s\",\"branch\":\"%s\"}",
                fileName, 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 " + fileName + " deleted successfully.");
            return 200;
        } else {
            log.error("Failed to delete file. Response code: " + responseCode);
            return responseCode;
        }
    }

    private SingleLanguagePackVO getSingleLanguagePack_new(String language, RepoType repoType) {
        log.info("[LanguageConfigServiceImpl :: getSingleLanguagePack] :: language pack for: {}", language);
        Map<String, Object> languagePack = languagePacks.get(language);
        FileContent fileContent = gitFileService.getFileContent(repoType, repoName, "/");
        SingleLanguagePackVO singleLanguagePackVO = gitFileService.parseFileContent(fileContent, "/", SingleLanguagePackVO.class);
        return SingleLanguagePackVO.builder().build();
    }

    @Override
    public List<SingleLanguagePackVO> getAllLanguagePackContents() {
        log.info("[LanguageConfigServiceImpl :: getAllLanguagePackContents] :: language packs: {}", languagePacks);

        List<SingleLanguagePackVO> languagePackVOList = new ArrayList<>();

        try {
            ensureLanguagePacksAvailable();

            // 각 언어팩별로 전체 내용 조회
            for (String language : availableLanguages) {
                try {
                    SingleLanguagePackVO languagePackVO = getSingleLanguagePack(language);
                    languagePackVOList.add(languagePackVO);
                } catch (Exception e) {
                    log.error("Error getting language pack for {}: {}", language, e.getMessage());
                    // 개별 언어팩 오류는 무시하고 계속 진행
                }
            }
            return languagePackVOList;
        } catch (Exception e) {
            log.error("Error getting all language pack contents: {}", e.getMessage(), e);
            throw new RuntimeException("Failed to get all language pack contents", e);
        }
    }

    @Override
    public List<SingleLanguagePackVO> refreshAllLanguagePackContents() {
        availableLanguages.clear();
        return this.getAllLanguagePackContents();
    }

    @Override
    public List<String> checkLanguageKeyExists(String language, List<String> keys) {
        if (keys == null || keys.isEmpty()) {
            log.error("The provided keys are null or empty. Please provide keys to check existence of keys.");
            throw new RuntimeException("The provided keys are null or empty. Please provide keys to check existence of keys.");
        }
        Set<String> keySet = new HashSet<>();

        ensureLanguagePacksAvailable();

        if (!availableLanguages.contains(language)) {
            SingleLanguagePackVO singleLanguagePack = this.getSingleLanguagePack(language);
            keySet = singleLanguagePack.getLanguagePack().keySet();
        } else {
            Map<String, Object> unFlatLanguagePack = languagePacks.get(language);
            keySet = LanguagePackFileReader.flattenLanguagePack(unFlatLanguagePack).keySet();
        }
        if (keySet.isEmpty()) {
            log.error("The provided language pack does not contain any keys.");
            throw new RuntimeException("The provided language pack does not contain any keys.");
        }

        return keys.parallelStream().filter(keySet::contains).collect(Collectors.toList());
    }

    /**
     * 모든 언어팩 파일을 동적으로 가져와서 캐시에 저장
     */
    public Map<String, Map<String, Object>> fetchLanguagePacks() {
        log.info("Fetching all language packs from Gitea");
        try {
            // Gitea에서 언어팩 파일 목록 조회
            // 언어팩 파일 url 목록 (fileURL)
            List<String> languagePackFileUrls = GiteaFileUtil.getJsonFileUrlsInDirectory(
                    languageRepositoryUrl, giteaUserConfig.getGitBranch(), giteaUserConfig.getGitUsername(), giteaUserConfig.getGitPassword());

            // 사용 가능한 언어 목록 업데이트
            availableLanguages = languagePackFileUrls.stream()
                    .map(fileUrl -> GiteaFileUtil.extractFileNameWithoutExtension(fileUrl).replace(".json", ""))
                    .collect(Collectors.toList());

            // 각 언어팩 파일 읽기
            languagePacks.clear();

            for (String fileUrl : languagePackFileUrls) {
                String fileName = GiteaFileUtil.extractFileNameWithoutExtension(fileUrl); // ko.json?ref=main ∵ fileUrl (not download_url)
                String languageCode = fileName.replace(".json?ref=main", "");

                // un-flat Json
                Map<String, Object> languagePack = LanguagePackFileReader.readLanguagePackFile(
                        fileUrl,
                        giteaUserConfig.getGitUsername(),
                        giteaUserConfig.getGitPassword());

                languagePacks.put(languageCode, languagePack);
            }

            lastRefreshTime = LocalDateTime.now();
            log.info("Successfully loaded {} language packs: {}", availableLanguages.size(), availableLanguages);

            return languagePacks;
        } catch (Exception e) {
            log.error("Error fetching language packs: {}", e.getMessage(), e);
            throw new RuntimeException("Failed to fetch language packs", e);
        }
    }


    /**
     * 언어팩 데이터가 있는지 확인하고 없으면 refresh를 시도하는 메서드
     * @throws RuntimeException 언어팩 데이터를 가져오지 못한 경우
     */
    private void ensureLanguagePacksAvailable() {
        if (availableLanguages.isEmpty()) {
            log.info("Available languages is empty, attempting to refresh language packs");
            fetchLanguagePacks();

            if (availableLanguages.isEmpty()) {
                log.warn("No language packs found after refresh");
                throw new RuntimeException("No language packs found after refresh");
            }
        }
    }
}
