package com.erp.service;

import com.erp.common.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Service
@Slf4j
public class MultilangService extends BaseService {

    private static final String NS = "multilang.";

    public List<Map<String, Object>> getLanguages() {
        return sqlSession.selectList(NS + "getMultilangLanguageList", new HashMap<>());
    }

    public Map<String, Object> createLanguage(Map<String, Object> params) {
        String langCode = str(params.get("lang_code"));
        Map<String, Object> check = Map.of("lang_code", langCode);
        if (sqlSession.selectOne(NS + "getMultilangLanguageInfo", check) != null) {
            throw new IllegalArgumentException("이미 존재하는 언어 코드입니다: " + langCode);
        }
        if (params.get("is_active") == null) params.put("is_active", "Y");
        if (params.get("sort_order") == null) params.put("sort_order", 0);
        sqlSession.insert(NS + "insertMultilangLanguage", params);
        return sqlSession.selectOne(NS + "getMultilangLanguageInfo", check);
    }

    public Map<String, Object> updateLanguage(String langCode, Map<String, Object> params) {
        params.put("lang_code", langCode);
        Map<String, Object> check = Map.of("lang_code", langCode);
        if (sqlSession.selectOne(NS + "getMultilangLanguageInfo", check) == null) {
            throw new IllegalArgumentException("언어를 찾을 수 없습니다: " + langCode);
        }
        sqlSession.update(NS + "updateMultilangLanguage", params);
        return sqlSession.selectOne(NS + "getMultilangLanguageInfo", check);
    }

    public String toggleLanguage(String langCode) {
        Map<String, Object> check = Map.of("lang_code", langCode);
        Map<String, Object> current = sqlSession.selectOne(NS + "getMultilangLanguageInfo", check);
        if (current == null) {
            throw new IllegalArgumentException("언어를 찾을 수 없습니다: " + langCode);
        }
        String newStatus = "Y".equals(current.get("is_active")) ? "N" : "Y";
        Map<String, Object> params = new HashMap<>();
        params.put("lang_code", langCode);
        params.put("new_status", newStatus);
        params.put("updated_by", "system");
        sqlSession.update(NS + "updateMultilangLanguageStatus", params);
        return "Y".equals(newStatus) ? "활성화" : "비활성화";
    }

    @Transactional
    public void deleteLanguage(String langCode) {
        Map<String, Object> check = Map.of("lang_code", langCode);
        if (sqlSession.selectOne(NS + "getMultilangLanguageInfo", check) == null) {
            throw new IllegalArgumentException("언어를 찾을 수 없습니다: " + langCode);
        }
        Map<String, Object> params = Map.of("lang_code", langCode);
        sqlSession.delete(NS + "deleteMultilangTextByLangCode", params);
        sqlSession.delete(NS + "deleteMultilangLanguage", params);
    }

    public List<Map<String, Object>> getLangKeys(Map<String, Object> params) {
        if (params.containsKey("company_code")) {
            params.put("filter_company_code", params.get("company_code"));
        }
        return sqlSession.selectList(NS + "getMultilangKeyList", params);
    }

    public List<Map<String, Object>> getLangTexts(int keyId) {
        return sqlSession.selectList(NS + "getMultilangTextList", Map.of("key_id", keyId));
    }

    public int createLangKey(Map<String, Object> params) {
        String companyCode = str(params.get("company_code"));
        String langKey = str(params.get("lang_key"));
        Map<String, Object> dupCheck = Map.of("company_code", companyCode, "lang_key", langKey);
        if (sqlSession.selectOne(NS + "getMultilangKeyByCompanyAndKey", dupCheck) != null) {
            throw new IllegalArgumentException("동일한 회사에 이미 존재하는 언어키입니다: " + langKey);
        }
        if (params.get("is_active") == null) params.put("is_active", "Y");
        sqlSession.insert(NS + "insertMultilangKey", params);
        return toInt(params.get("key_id"));
    }

    public void updateLangKey(int keyId, Map<String, Object> params) {
        params.put("key_id", keyId);
        if (sqlSession.selectOne(NS + "getMultilangKeyInfo", Map.of("key_id", keyId)) == null) {
            throw new IllegalArgumentException("다국어 키를 찾을 수 없습니다: " + keyId);
        }
        String companyCode = str(params.get("company_code"));
        String langKey = str(params.get("lang_key"));
        if (!companyCode.isEmpty() && !langKey.isEmpty()) {
            Map<String, Object> dupCheck = Map.of("company_code", companyCode, "lang_key", langKey, "key_id", keyId);
            if (sqlSession.selectOne(NS + "getMultilangKeyByCompanyAndKeyExclude", dupCheck) != null) {
                throw new IllegalArgumentException("동일한 회사에 이미 존재하는 언어키입니다: " + langKey);
            }
        }
        sqlSession.update(NS + "updateMultilangKey", params);
    }

    @Transactional
    public void deleteLangKey(int keyId) {
        if (sqlSession.selectOne(NS + "getMultilangKeyInfo", Map.of("key_id", keyId)) == null) {
            throw new IllegalArgumentException("다국어 키를 찾을 수 없습니다: " + keyId);
        }
        Map<String, Object> params = Map.of("key_id", keyId);
        sqlSession.delete(NS + "deleteMultilangTextByKeyId", params);
        sqlSession.delete(NS + "deleteMultilangKey", params);
    }

    public String toggleLangKey(int keyId) {
        Map<String, Object> current = sqlSession.selectOne(NS + "getMultilangKeyInfo", Map.of("key_id", keyId));
        if (current == null) {
            throw new IllegalArgumentException("다국어 키를 찾을 수 없습니다: " + keyId);
        }
        String newStatus = "Y".equals(current.get("is_active")) ? "N" : "Y";
        Map<String, Object> params = new HashMap<>();
        params.put("key_id", keyId);
        params.put("new_status", newStatus);
        params.put("updated_by", "system");
        sqlSession.update(NS + "updateMultilangKeyStatus", params);
        return "Y".equals(newStatus) ? "활성화" : "비활성화";
    }

    @Transactional
    public void saveLangTexts(int keyId, List<Map<String, Object>> texts) {
        if (sqlSession.selectOne(NS + "getMultilangKeyInfo", Map.of("key_id", keyId)) == null) {
            throw new IllegalArgumentException("다국어 키를 찾을 수 없습니다: " + keyId);
        }
        sqlSession.delete(NS + "deleteMultilangTextByKeyId", Map.of("key_id", keyId));
        for (Map<String, Object> text : texts) {
            Map<String, Object> params = new HashMap<>(text);
            params.put("key_id", keyId);
            if (params.get("is_active") == null) params.put("is_active", "Y");
            sqlSession.insert(NS + "insertMultilangText", params);
        }
    }

    public String getUserText(String companyCode, String menuCode, String langKey, String userLang) {
        Map<String, Object> params = Map.of(
                "company_code", companyCode, "menu_code", menuCode,
                "lang_key", langKey, "user_lang", userLang);
        Map<String, Object> result = sqlSession.selectOne(NS + "getMultilangUserText", params);
        return result != null ? str(result.get("lang_text")) : langKey;
    }

    public String getLangText(String companyCode, String langKey, String langCode) {
        Map<String, Object> params = Map.of(
                "company_code", companyCode, "lang_key", langKey, "lang_code", langCode);
        Map<String, Object> result = sqlSession.selectOne(NS + "getMultilangSingleText", params);
        return result != null ? str(result.get("lang_text")) : langKey;
    }

    public Map<String, String> getBatchTranslations(String companyCode, String menuCode,
                                                     String userLang, List<String> langKeys) {
        if (langKeys == null || langKeys.isEmpty()) return new LinkedHashMap<>();
        Map<String, Object> params = new HashMap<>();
        params.put("company_code", companyCode);
        params.put("menu_code", menuCode);
        params.put("user_lang", userLang);
        params.put("lang_keys", langKeys);
        List<Map<String, Object>> translations = sqlSession.selectList(NS + "getMultilangBatchTranslationList", params);
        Map<String, String> result = new LinkedHashMap<>();
        Set<String> processed = new HashSet<>();
        for (Map<String, Object> t : translations) {
            String key = str(t.get("lang_key"));
            if (langKeys.contains(key) && !processed.contains(key)) {
                result.put(key, str(t.get("lang_text")));
                processed.add(key);
            }
        }
        List<String> missingKeys = langKeys.stream().filter(k -> !processed.contains(k)).toList();
        if (!missingKeys.isEmpty() && !"KR".equals(userLang)) {
            Map<String, Object> fallbackParams = new HashMap<>();
            fallbackParams.put("company_code", companyCode);
            fallbackParams.put("missing_keys", missingKeys);
            List<Map<String, Object>> fallback = sqlSession.selectList(NS + "getMultilangFallbackTranslationList", fallbackParams);
            Set<String> fbProcessed = new HashSet<>();
            for (Map<String, Object> t : fallback) {
                String key = str(t.get("lang_key"));
                if (!result.containsKey(key) && !fbProcessed.contains(key)) {
                    result.put(key, str(t.get("lang_text")));
                    fbProcessed.add(key);
                }
            }
        }
        for (String key : langKeys) result.putIfAbsent(key, key);
        return result;
    }

    public List<Map<String, Object>> getCategories() {
        List<Map<String, Object>> flat = sqlSession.selectList(NS + "getMultilangCategoryList", new HashMap<>());
        return buildCategoryTree(flat);
    }

    public Map<String, Object> getCategoryById(int categoryId) {
        return sqlSession.selectOne(NS + "getMultilangCategoryInfo", Map.of("category_id", categoryId));
    }

    public List<Map<String, Object>> getCategoryPath(int categoryId) {
        return sqlSession.selectList(NS + "getMultilangCategoryPath", Map.of("category_id", categoryId));
    }

    private List<Map<String, Object>> buildCategoryTree(List<Map<String, Object>> flat) {
        Map<Integer, Map<String, Object>> byId = new LinkedHashMap<>();
        for (Map<String, Object> cat : flat) {
            Map<String, Object> node = new LinkedHashMap<>(cat);
            node.put("children", new ArrayList<>());
            byId.put(toInt(cat.get("category_id")), node);
        }
        List<Map<String, Object>> roots = new ArrayList<>();
        for (Map<String, Object> node : byId.values()) {
            Object parentIdObj = node.get("parent_id");
            if (parentIdObj == null) {
                roots.add(node);
            } else {
                int parentId = toInt(parentIdObj);
                Map<String, Object> parent = byId.get(parentId);
                if (parent != null) {
                    @SuppressWarnings("unchecked")
                    List<Map<String, Object>> children = (List<Map<String, Object>>) parent.get("children");
                    children.add(node);
                } else {
                    roots.add(node);
                }
            }
        }
        return roots;
    }

    @Transactional
    public int generateKey(Map<String, Object> params) {
        int categoryId = toInt(params.get("category_id"));
        String keyMeaning = str(params.get("key_meaning"));
        String companyCode = str(params.get("company_code"));
        List<Map<String, Object>> categoryPath = getCategoryPath(categoryId);
        if (categoryPath.isEmpty()) throw new IllegalArgumentException("존재하지 않는 카테고리입니다");
        String langKey = buildLangKey(categoryPath, keyMeaning);
        if (sqlSession.selectOne(NS + "getMultilangKeyByCompanyAndKey", Map.of("company_code", companyCode, "lang_key", langKey)) != null) {
            throw new IllegalArgumentException("이미 존재하는 키입니다: " + langKey);
        }
        Map<String, Object> insertParams = new HashMap<>();
        insertParams.put("company_code", companyCode);
        insertParams.put("lang_key", langKey);
        insertParams.put("category_id", categoryId);
        insertParams.put("key_meaning", keyMeaning);
        insertParams.put("usage_note", params.get("usage_note"));
        insertParams.put("description", params.get("usage_note"));
        insertParams.put("created_by", params.getOrDefault("created_by", "system"));
        sqlSession.insert(NS + "insertMultilangKeyWithCategory", insertParams);
        int keyId = toInt(insertParams.get("key_id"));
        @SuppressWarnings("unchecked")
        List<Map<String, Object>> texts = (List<Map<String, Object>>) params.get("texts");
        if (texts != null) {
            for (Map<String, Object> text : texts) {
                Map<String, Object> tp = new HashMap<>(text);
                tp.put("key_id", keyId);
                if (tp.get("is_active") == null) tp.put("is_active", "Y");
                if (tp.get("created_by") == null) tp.put("created_by", params.getOrDefault("created_by", "system"));
                if (tp.get("updated_by") == null) tp.put("updated_by", params.getOrDefault("created_by", "system"));
                sqlSession.insert(NS + "insertMultilangText", tp);
            }
        }
        return keyId;
    }

    public Map<String, Object> previewKey(int categoryId, String keyMeaning, String companyCode) {
        List<Map<String, Object>> categoryPath = getCategoryPath(categoryId);
        if (categoryPath.isEmpty()) throw new IllegalArgumentException("존재하지 않는 카테고리입니다");
        String langKey = buildLangKey(categoryPath, keyMeaning);
        Map<String, Object> commonKey = sqlSession.selectOne(NS + "getMultilangKeyByCompanyAndKey", Map.of("company_code", "*", "lang_key", langKey));
        Map<String, Object> companyKey = sqlSession.selectOne(NS + "getMultilangKeyByCompanyAndKey", Map.of("company_code", companyCode, "lang_key", langKey));
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("lang_key", langKey);
        result.put("exists", companyKey != null);
        result.put("is_override", commonKey != null && companyKey == null);
        if (commonKey != null) result.put("base_key_id", toInt(commonKey.get("key_id")));
        return result;
    }

    @Transactional
    public int createOverrideKey(Map<String, Object> params) {
        int baseKeyId = toInt(params.get("base_key_id"));
        String companyCode = str(params.get("company_code"));
        Map<String, Object> baseKey = sqlSession.selectOne(NS + "getMultilangBaseKeyInfo", Map.of("key_id", baseKeyId));
        if (baseKey == null) throw new IllegalArgumentException("원본 키를 찾을 수 없습니다");
        if (!"*".equals(str(baseKey.get("company_code")))) throw new IllegalArgumentException("공통 키(*)만 오버라이드 할 수 있습니다");
        String langKey = str(baseKey.get("lang_key"));
        if (sqlSession.selectOne(NS + "getMultilangKeyByCompanyAndKey", Map.of("company_code", companyCode, "lang_key", langKey)) != null) {
            throw new IllegalArgumentException("이미 해당 회사의 오버라이드 키가 존재합니다");
        }
        Map<String, Object> insertParams = new HashMap<>();
        insertParams.put("company_code", companyCode);
        insertParams.put("lang_key", langKey);
        insertParams.put("category_id", baseKey.get("category_id"));
        insertParams.put("key_meaning", baseKey.get("key_meaning"));
        insertParams.put("base_key_id", baseKeyId);
        insertParams.put("created_by", params.getOrDefault("created_by", "system"));
        sqlSession.insert(NS + "insertMultilangOverrideKey", insertParams);
        int keyId = toInt(insertParams.get("key_id"));
        @SuppressWarnings("unchecked")
        List<Map<String, Object>> texts = (List<Map<String, Object>>) params.get("texts");
        if (texts != null) {
            for (Map<String, Object> text : texts) {
                Map<String, Object> tp = new HashMap<>(text);
                tp.put("key_id", keyId);
                if (tp.get("is_active") == null) tp.put("is_active", "Y");
                if (tp.get("created_by") == null) tp.put("created_by", params.getOrDefault("created_by", "system"));
                if (tp.get("updated_by") == null) tp.put("updated_by", params.getOrDefault("created_by", "system"));
                sqlSession.insert(NS + "insertMultilangText", tp);
            }
        }
        return keyId;
    }

    public List<Map<String, Object>> getOverrideKeys(String companyCode) {
        return sqlSession.selectList(NS + "getMultilangOverrideKeyList", Map.of("company_code", companyCode));
    }

    @Transactional
    public List<Map<String, Object>> generateScreenLabelKeys(Map<String, Object> params) {
        int screenId = toInt(params.get("screen_id"));
        Map<String, Object> screenInfo = sqlSession.selectOne(NS + "getMultilangScreenCompanyCode", Map.of("screen_id", screenId));
        String companyCode = (screenInfo != null && !str(screenInfo.get("company_code")).isEmpty())
                ? str(screenInfo.get("company_code")) : str(params.getOrDefault("company_code", "*"));
        String companyName;
        if ("*".equals(companyCode)) {
            companyName = "공통";
        } else {
            Map<String, Object> companyInfo = sqlSession.selectOne(NS + "getMultilangCompanyName", Map.of("company_code", companyCode));
            companyName = (companyInfo != null) ? str(companyInfo.get("company_name")) : companyCode;
        }
        List<String> groupPath = getScreenGroupPath(screenId);
        int categoryId = ensureScreenGroupCategory(companyCode, companyName, groupPath);
        List<Map<String, Object>> categoryPath = getCategoryPath(categoryId);
        List<String> keyPrefixParts = new ArrayList<>();
        for (Map<String, Object> c : categoryPath) keyPrefixParts.add(str(c.get("key_prefix")));
        @SuppressWarnings("unchecked")
        List<Map<String, Object>> labels = (List<Map<String, Object>>) params.get("labels");
        List<Map<String, Object>> results = new ArrayList<>();
        for (Map<String, Object> labelInfo : labels) {
            String label = str(labelInfo.get("label"));
            String componentId = str(labelInfo.get("component_id"));
            String keyMeaning = labelToKeyMeaning(label);
            List<String> parts = new ArrayList<>(keyPrefixParts);
            parts.add(keyMeaning);
            String langKey = String.join(".", parts);
            Map<String, Object> existing = sqlSession.selectOne(NS + "getMultilangKeyByCompanyAndKey", Map.of("company_code", companyCode, "lang_key", langKey));
            int keyId;
            if (existing != null) {
                keyId = toInt(existing.get("key_id"));
            } else {
                Map<String, Object> insertParams = new HashMap<>();
                insertParams.put("company_code", companyCode);
                insertParams.put("lang_key", langKey);
                insertParams.put("description", "화면 " + screenId + "의 " + str(labelInfo.getOrDefault("type", "라벨")) + ": " + label);
                insertParams.put("is_active", "Y");
                insertParams.put("category_id", categoryId);
                insertParams.put("key_meaning", keyMeaning);
                sqlSession.insert(NS + "insertMultilangKeyWithCategory", insertParams);
                keyId = toInt(insertParams.get("key_id"));
                sqlSession.update(NS + "upsertMultilangText", Map.of("key_id", keyId, "lang_code", "KR", "lang_text", label));
            }
            Map<String, Object> entry = new LinkedHashMap<>();
            entry.put("component_id", componentId);
            entry.put("key_id", keyId);
            entry.put("lang_key", langKey);
            results.add(entry);
        }
        return results;
    }

    private List<String> getScreenGroupPath(int screenId) {
        Map<String, Object> groupRow = sqlSession.selectOne(NS + "getMultilangScreenGroupId", Map.of("screen_id", screenId));
        if (groupRow == null) return Collections.emptyList();
        int groupId = toInt(groupRow.get("group_id"));
        List<Map<String, Object>> groups = sqlSession.selectList(NS + "getMultilangScreenGroupPath", Map.of("group_id", groupId));
        List<String> path = new ArrayList<>();
        for (Map<String, Object> g : groups) path.add(str(g.get("group_name")));
        return path;
    }

    private int ensureScreenGroupCategory(String companyCode, String companyName, List<String> groupPath) {
        if (groupPath.isEmpty()) return ensureCompanyCategory(companyCode, companyName);
        int parentId = ensureCompanyCategory(companyCode, companyName);
        int currentLevel = 3;
        for (String groupName : groupPath) {
            Map<String, Object> existing = sqlSession.selectOne(NS + "getMultilangCategoryByNameAndParent", Map.of("category_name", groupName, "parent_id", parentId));
            if (existing != null) {
                parentId = toInt(existing.get("category_id"));
            } else {
                Map<String, Object> insertParams = new HashMap<>();
                insertParams.put("category_code", (companyCode + "_GROUP_" + groupName).replace("\\s+", "_"));
                insertParams.put("category_name", groupName);
                insertParams.put("parent_id", parentId);
                insertParams.put("level", currentLevel);
                insertParams.put("key_prefix", groupName.toLowerCase().replace("\\s+", "_"));
                insertParams.put("description", groupName + " 화면 그룹의 다국어");
                insertParams.put("sort_order", 0);
                sqlSession.insert(NS + "insertMultilangCategory", insertParams);
                parentId = toInt(insertParams.get("category_id"));
            }
            currentLevel++;
        }
        return parentId;
    }

    private int ensureCompanyCategory(String companyCode, String companyName) {
        int screenRootId = ensureScreenRootCategory();
        Map<String, Object> existing = sqlSession.selectOne(NS + "getMultilangCategoryByCodeAndParent", Map.of("category_code", companyCode, "parent_id", screenRootId));
        if (existing != null) return toInt(existing.get("category_id"));
        String displayName = "*".equals(companyCode) ? "공통" : companyName;
        String keyPrefix = "*".equals(companyCode) ? "common" : companyCode.toLowerCase();
        Map<String, Object> insertParams = new HashMap<>();
        insertParams.put("category_code", companyCode);
        insertParams.put("category_name", displayName);
        insertParams.put("parent_id", screenRootId);
        insertParams.put("level", 2);
        insertParams.put("key_prefix", keyPrefix);
        insertParams.put("description", displayName + " 회사의 화면 다국어");
        insertParams.put("sort_order", "*".equals(companyCode) ? 0 : 10);
        sqlSession.insert(NS + "insertMultilangCategory", insertParams);
        return toInt(insertParams.get("category_id"));
    }

    private int ensureScreenRootCategory() {
        Map<String, Object> existing = sqlSession.selectOne(NS + "getMultilangRootCategoryByCode", Map.of("category_code", "screen"));
        if (existing != null) return toInt(existing.get("category_id"));
        Map<String, Object> insertParams = new HashMap<>();
        insertParams.put("category_code", "screen");
        insertParams.put("category_name", "화면");
        insertParams.put("parent_id", null);
        insertParams.put("level", 1);
        insertParams.put("key_prefix", "screen");
        insertParams.put("description", "화면 디자이너에서 자동 생성된 다국어 키");
        insertParams.put("sort_order", 100);
        sqlSession.insert(NS + "insertMultilangCategory", insertParams);
        return toInt(insertParams.get("category_id"));
    }

    private String buildLangKey(List<Map<String, Object>> categoryPath, String keyMeaning) {
        List<String> parts = new ArrayList<>();
        for (Map<String, Object> c : categoryPath) parts.add(str(c.get("key_prefix")));
        parts.add(keyMeaning);
        return String.join(".", parts);
    }

    private String labelToKeyMeaning(String label) {
        if (label.matches("^[a-z][a-z0-9_]*$")) return label;
        if (label.matches("^[A-Za-z][A-Za-z0-9 ]*$")) return label.toLowerCase().replace(" ", "_");
        return label.replaceAll("[^\\w가-힣\\s]", "").replaceAll("\\s+", "_").toLowerCase();
    }

    private String str(Object o) { return o == null ? "" : o.toString(); }

    private int toInt(Object o) {
        if (o == null) return 0;
        if (o instanceof Number) return ((Number) o).intValue();
        try { return Integer.parseInt(o.toString()); } catch (NumberFormatException e) { return 0; }
    }
}
