package com.erp.service;

import com.erp.common.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AdminService extends BaseService {

    @Autowired
    private CommonService commonService;
    @Autowired
    private PasswordEncoder passwordEncoder;

    // ── 메뉴 관리 ──────────────────────────────────────────────────────────

    public List<Map<String, Object>> getAdminMenuList(Map<String, Object> params) {
        params.putIfAbsent("user_lang", "ko");
        // String "false"/"true" → Boolean (OGNL !includeInactive 오작동 방지)
        Object includeInactiveRaw = params.get("include_inactive");
        params.put("include_inactive", Boolean.TRUE.equals(includeInactiveRaw) || "true".equals(includeInactiveRaw));
        params.putIfAbsent("is_management_screen", false);
        return sqlSession.selectList("admin.selectAdminMenuList", params);
    }

    public List<Map<String, Object>> getUserMenuList(Map<String, Object> params) {
        params.putIfAbsent("user_lang", "ko");
        // 관리자(SUPER_ADMIN/COMPANY_ADMIN/ADMIN)는 모든 메뉴, 일반 사용자는 권한 그룹 연결 메뉴만
        String userType = (String) params.get("user_type");
        boolean isAdmin = "SUPER_ADMIN".equals(userType)
                || "COMPANY_ADMIN".equals(userType)
                || "ADMIN".equals(userType);
        if (isAdmin) {
            return sqlSession.selectList("admin.selectUserMenuList", params);
        }
        return sqlSession.selectList("admin.selectUserMenuListByRole", params);
    }

    public Map<String, Object> getPopMenuList(Map<String, Object> params) {
        Map<String, Object> result = new HashMap<>();
        List<Map<String, Object>> parents = sqlSession.selectList("admin.selectPopParentMenu", params);
        if (parents.isEmpty()) {
            result.put("parent_menu", null);
            result.put("child_menus", List.of());
            result.put("landing_menu", null);
            return result;
        }
        Map<String, Object> parent = parents.get(0);
        Object parentObjid = parent.get("objid");
        Object parentCompanyCode = parent.get("company_code");

        Map<String, Object> childParams = new HashMap<>();
        childParams.put("parent_objid", parentObjid);
        childParams.put("parent_company_code", parentCompanyCode);
        List<Map<String, Object>> children = sqlSession.selectList("admin.selectPopChildMenus", childParams);

        Map<String, Object> landingMenu = children.stream()
                .filter(m -> {
                    Object desc = m.get("menu_desc");
                    return desc != null && desc.toString().contains("[POP_LANDING]");
                })
                .findFirst()
                .orElse(null);

        result.put("parent_menu", parent);
        result.put("child_menus", children);
        result.put("landing_menu", landingMenu);
        return result;
    }

    public Map<String, Object> getMenuInfo(String menuId) {
        Map<String, Object> params = new HashMap<>();
        params.put("menu_id", menuId);
        return sqlSession.selectOne("admin.selectMenuById", params);
    }

    public Map<String, Object> saveMenu(Map<String, Object> params) {
        // Generate timestamp-based objid
        long objid = System.currentTimeMillis();
        params.put("objid", objid);

        normalizeMenuParams(params);

        // Map frontend field names to DB column names
        if (params.get("menu_name_kor") == null && params.get("menu_name") != null) {
            params.put("menu_name_kor", params.get("menu_name"));
        }

        sqlSession.insert("admin.insertMenu", params);
        return params;
    }

    public Map<String, Object> updateMenu(String menuId, Map<String, Object> params) {
        params.put("menu_id", menuId);
        normalizeMenuParams(params);
        sqlSession.update("admin.updateMenu", params);
        return params;
    }

    // 프론트가 보내는 camelCase 키를 XML이 기대하는 snake_case 키로 복사한다.
    // (XML #{menu_name_kor} 등 snake_case 파라미터로 바인딩하기 위함)
    private void normalizeMenuParams(Map<String, Object> params) {
        copyIfAbsent(params, "menuNameKor", "menu_name_kor");
        copyIfAbsent(params, "menuUrl", "menu_url");
        copyIfAbsent(params, "menuDesc", "menu_desc");
        copyIfAbsent(params, "parentObjId", "parent_obj_id");
        copyIfAbsent(params, "langKey", "lang_key");
        copyIfAbsent(params, "langKeyDesc", "lang_key_desc");
        copyIfAbsent(params, "menuIcon", "menu_icon");
        copyIfAbsent(params, "screenCode", "screen_code");
        copyIfAbsent(params, "menuType", "menu_type");
        copyIfAbsent(params, "companyCode", "company_code");
    }

    private void copyIfAbsent(Map<String, Object> params, String camelKey, String snakeKey) {
        if (params.get(snakeKey) == null && params.get(camelKey) != null) {
            params.put(snakeKey, params.get(camelKey));
        }
    }

    public void deleteMenu(String menuId) {
        Map<String, Object> params = new HashMap<>();
        params.put("menu_id", menuId);
        sqlSession.delete("admin.deleteMenu", params);
    }

    public Map<String, Object> toggleMenuStatus(String menuId) {
        Map<String, Object> current = getMenuInfo(menuId);
        if (current == null) throw new IllegalArgumentException("메뉴를 찾을 수 없습니다.");
        String newStatus = "active".equalsIgnoreCase((String) current.get("status")) ? "inactive" : "active";
        Map<String, Object> params = new HashMap<>();
        params.put("menu_id", menuId);
        params.put("status", newStatus);
        sqlSession.update("admin.updateMenuStatus", params);
        return Map.of("status", newStatus);
    }

    // ── 사용자 관리 ────────────────────────────────────────────────────────

    public Map<String, Object> getUserList(Map<String, Object> params) {
        String rawSearch = (String) params.get("search");
        if (rawSearch != null && !rawSearch.isBlank()) {
            params.put("search", "%" + rawSearch.trim() + "%");
        }
        commonService.applyPagination(params);

        List<Map<String, Object>> users = sqlSession.selectList("admin.selectUserList", params);
        Number countNum = sqlSession.selectOne("admin.selectUserCount", params);
        int total = countNum != null ? countNum.intValue() : 0;

        int page = params.get("page") != null ? Integer.parseInt(params.get("page").toString()) : 1;
        int limit = params.get("limit") != null ? Integer.parseInt(params.get("limit").toString()) : 20;
        int totalPages = (int) Math.ceil((double) total / limit);

        Map<String, Object> pagination = new HashMap<>();
        pagination.put("page", page);
        pagination.put("limit", limit);
        pagination.put("total_pages", totalPages);

        Map<String, Object> result = new HashMap<>();
        result.put("data", users);
        result.put("total", total);
        result.put("search_type", rawSearch != null ? "v2" : "none");
        result.put("pagination", pagination);
        return result;
    }

    public Map<String, Object> getUserInfo(String userId) {
        Map<String, Object> params = new HashMap<>();
        params.put("user_id", userId);
        return sqlSession.selectOne("admin.selectUserById", params);
    }

    public List<Map<String, Object>> getUserHistory(String userId, Map<String, Object> params) {
        params.put("user_id", userId);
        commonService.applyPagination(params);
        return sqlSession.selectList("admin.selectUserHistory", params);
    }

    @Transactional
    public Map<String, Object> saveUser(Map<String, Object> params) {
        String userId = (String) params.get("user_id");
        Map<String, Object> existing = sqlSession.selectOne("admin.selectUserById", Map.of("user_id", userId));
        if (existing != null) {
            sqlSession.update("admin.updateUser", params);
        } else {
            String rawPw = (String) params.getOrDefault("user_password", "Welcome1!");
            params.put("user_password", passwordEncoder.encode(rawPw));
            sqlSession.insert("admin.insertUser", params);
        }
        return params;
    }

    public void changeUserStatus(String userId, String status) {
        Map<String, Object> params = new HashMap<>();
        params.put("user_id", userId);
        params.put("status", status);
        sqlSession.update("admin.updateUserStatus", params);
    }

    public void resetUserPassword(String userId) {
        resetUserPassword(userId, null);
    }

    public void resetUserPassword(String userId, String newPassword) {
        if (userId == null || userId.isBlank()) {
            throw new IllegalArgumentException("user_id 는 필수입니다");
        }
        String rawPw = (newPassword != null && !newPassword.isBlank()) ? newPassword : "Welcome1!";
        Map<String, Object> params = new HashMap<>();
        params.put("user_id", userId);
        params.put("user_password", passwordEncoder.encode(rawPw));
        sqlSession.update("admin.updateUserPassword", params);
    }

    public Map<String, Object> getUserLocale(String userId) {
        Map<String, Object> row = sqlSession.selectOne("admin.selectUserLocale", Map.of("user_id", userId));
        Map<String, Object> result = new HashMap<>();
        result.put("locale", row != null ? row.get("locale") : "KR");
        return result;
    }

    @Transactional
    public void setUserLocale(String userId, String locale) {
        Map<String, Object> valid = sqlSession.selectOne("admin.selectValidLocale", Map.of("locale", locale));
        if (valid == null) {
            throw new IllegalArgumentException("유효하지 않은 로케일입니다: " + locale);
        }
        Map<String, Object> params = new HashMap<>();
        params.put("user_id", userId);
        params.put("locale", locale);
        sqlSession.update("admin.updateUserLocale", params);
    }

    public Map<String, Object> getUserWithDept(Map<String, Object> params) {
        Map<String, Object> userInfo = sqlSession.selectOne("admin.selectUserById", params);
        if (userInfo == null) return null;
        List<Map<String, Object>> depts = sqlSession.selectList("admin.selectUserDeptList", params);
        Map<String, Object> mainDept = depts.stream()
                .filter(d -> Boolean.TRUE.equals(d.get("is_primary")))
                .findFirst().orElse(null);
        List<Map<String, Object>> subDepts = depts.stream()
                .filter(d -> !Boolean.TRUE.equals(d.get("is_primary")))
                .collect(Collectors.toList());
        Map<String, Object> result = new HashMap<>();
        result.put("user_info", userInfo);
        result.put("main_dept", mainDept);
        result.put("sub_depts", subDepts);
        return result;
    }

    @Transactional
    public Map<String, Object> saveUserWithDept(Map<String, Object> body) {
        @SuppressWarnings("unchecked")
        Map<String, Object> userInfoRaw = (Map<String, Object>) body.get("user_info");
        @SuppressWarnings("unchecked")
        Map<String, Object> mainDept = (Map<String, Object>) body.get("main_dept");
        @SuppressWarnings("unchecked")
        List<Map<String, Object>> subDepts = body.get("sub_depts") != null
                ? (List<Map<String, Object>>) body.get("sub_depts") : List.of();
        String companyCode = (String) body.get("company_code");

        if (userInfoRaw == null) throw new IllegalArgumentException("userInfo is required");

        // snake_case / camelCase 양쪽 처리
        String userId = getField(userInfoRaw, "user_id", "user_id");
        if (userId == null) throw new IllegalArgumentException("userId is required");

        // 메인 부서 코드/이름/직위 결정
        String deptCode = mainDept != null ? getField(mainDept, "dept_code", "dept_code") : null;
        if (deptCode == null) deptCode = getField(userInfoRaw, "dept_code", "dept_code");
        String deptName = mainDept != null ? getField(mainDept, "dept_name", "dept_name") : null;
        if (deptName == null) deptName = getField(userInfoRaw, "dept_name", "dept_name");
        String positionName = mainDept != null ? getField(mainDept, "position_name", "position_name") : null;
        if (positionName == null) positionName = getField(userInfoRaw, "position_name", "position_name");

        // camelCase params 빌드
        Map<String, Object> saveParams = new HashMap<>();
        saveParams.put("user_id", userId);
        saveParams.put("user_name", getField(userInfoRaw, "user_name", "user_name"));
        saveParams.put("user_name_eng", getField(userInfoRaw, "user_name_eng", "user_name_eng"));
        saveParams.put("email", userInfoRaw.getOrDefault("email", null));
        saveParams.put("tel", userInfoRaw.getOrDefault("tel", null));
        saveParams.put("cell_phone", getField(userInfoRaw, "cell_phone", "cell_phone"));
        saveParams.put("sabun", userInfoRaw.getOrDefault("sabun", null));
        saveParams.put("user_type", getField(userInfoRaw, "user_type", "user_type"));
        saveParams.put("user_type_name", getField(userInfoRaw, "user_type_name", "user_type_name"));
        saveParams.put("status", userInfoRaw.getOrDefault("status", "active"));
        saveParams.put("locale", userInfoRaw.getOrDefault("locale", null));
        saveParams.put("position_code", getField(userInfoRaw, "position_code", "position_code"));
        saveParams.put("dept_code", deptCode);
        saveParams.put("dept_name", deptName);
        saveParams.put("position_name", positionName);
        String effectiveCompany = !"*".equals(companyCode) ? companyCode : null;
        saveParams.put("company_code", effectiveCompany);

        // 기존 사용자 확인
        Map<String, Object> existing = sqlSession.selectOne("admin.selectUserById", Map.of("user_id", userId));
        boolean isUpdate = existing != null;

        if (isUpdate) {
            // 비밀번호가 제공된 경우만 업데이트
            String rawPw = getField(userInfoRaw, "user_password", "user_password");
            if (rawPw != null && !rawPw.isBlank()) {
                saveParams.put("user_password", passwordEncoder.encode(rawPw));
            }
            sqlSession.update("admin.updateUserForDept", saveParams);
        } else {
            String rawPw = getField(userInfoRaw, "user_password", "user_password");
            if (rawPw == null || rawPw.isBlank()) rawPw = "Welcome1!";
            saveParams.put("user_password", passwordEncoder.encode(rawPw));
            sqlSession.insert("admin.insertUser", saveParams);
        }

        // user_dept 처리
        if (mainDept != null || !subDepts.isEmpty()) {
            // 1. 기존 부서 목록 조회
            List<Map<String, Object>> existingDepts = sqlSession.selectList("admin.selectUserDeptList", Map.of("user_id", userId));
            Map<String, Object> existingMain = existingDepts.stream()
                    .filter(d -> Boolean.TRUE.equals(d.get("is_primary")))
                    .findFirst().orElse(null);

            // 2. 메인 부서 변경 시 기존 메인을 겸직으로 전환
            if (mainDept != null && deptCode != null && existingMain != null) {
                String existingMainCode = (String) existingMain.get("dept_code");
                if (existingMainCode != null && !existingMainCode.equals(deptCode)) {
                    sqlSession.update("admin.updateUserDeptNotPrimary", Map.of("user_id", userId, "dept_code", existingMainCode));
                }
            }

            // 3. 겸직 부서 전체 삭제 (새로 입력받은 것으로 교체)
            sqlSession.delete("admin.deleteUserDeptSub", Map.of("user_id", userId));

            // 4. 메인 부서 UPSERT
            if (mainDept != null && deptCode != null) {
                String userName = (String) saveParams.get("user_name");
                Map<String, Object> dp = new HashMap<>();
                dp.put("user_id", userId);
                dp.put("dept_code", deptCode);
                dp.put("is_primary", true);
                dp.put("dept_name", deptName);
                dp.put("user_name", userName);
                dp.put("position_name", positionName);
                dp.put("company_code", effectiveCompany);
                sqlSession.insert("admin.upsertUserDept", dp);
            }

            // 5. 겸직 부서 저장
            String userName = (String) saveParams.get("user_name");
            for (Map<String, Object> sub : subDepts) {
                String subCode = getField(sub, "dept_code", "dept_code");
                if (subCode == null || subCode.equals(deptCode)) continue;
                Map<String, Object> dp = new HashMap<>();
                dp.put("user_id", userId);
                dp.put("dept_code", subCode);
                dp.put("is_primary", false);
                dp.put("dept_name", getField(sub, "dept_name", "dept_name"));
                dp.put("user_name", userName);
                dp.put("position_name", getField(sub, "position_name", "position_name"));
                dp.put("company_code", effectiveCompany);
                sqlSession.insert("admin.upsertUserDept", dp);
            }
        }

        Map<String, Object> result = new HashMap<>();
        result.put("user_id", userId);
        result.put("is_update", isUpdate);
        return result;
    }

    /** snake_case / camelCase 양쪽에서 값 추출 헬퍼 */
    private String getField(Map<String, Object> map, String camelKey, String snakeKey) {
        Object val = map.get(camelKey);
        if (val == null) val = map.get(snakeKey);
        return val != null ? val.toString() : null;
    }

    // ── 부서 관리 ──────────────────────────────────────────────────────────

    public Map<String, Object> getDepartmentList(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);
        List<Map<String, Object>> rawList = sqlSession.selectList("admin.selectDepartmentList", params);

        // 평탄 목록
        List<Map<String, Object>> flatList = new java.util.ArrayList<>(rawList);

        // 트리 구조 빌드
        java.util.LinkedHashMap<String, Map<String, Object>> deptTreeMap = new java.util.LinkedHashMap<>();
        List<Map<String, Object>> rootDepts = new java.util.ArrayList<>();

        for (Map<String, Object> raw : rawList) {
            Map<String, Object> node = new java.util.LinkedHashMap<>(raw);
            node.put("children", new java.util.ArrayList<>());
            deptTreeMap.put((String) raw.get("dept_code"), node);
        }

        // 2차: parent_dept_code 기준 트리 연결
        for (Map<String, Object> raw : rawList) {
            String deptCode = (String) raw.get("dept_code");
            String parentDeptCode = (String) raw.get("parent_dept_code");
            Map<String, Object> node = deptTreeMap.get(deptCode);

            if (parentDeptCode != null && deptTreeMap.containsKey(parentDeptCode)) {
                @SuppressWarnings("unchecked")
                List<Map<String, Object>> children = (List<Map<String, Object>>) deptTreeMap.get(parentDeptCode).get("children");
                children.add(node);
            } else {
                rootDepts.add(node);
            }
        }

        Map<String, Object> result = new HashMap<>();
        result.put("departments", rootDepts);
        result.put("flat_list", flatList);
        result.put("total", rawList.size());
        result.put("total_count", rawList.size());
        return result;
    }

    // ── 회사 관리 ──────────────────────────────────────────────────────────

    public List<Map<String, Object>> getCompanyList() {
        List<Map<String, Object>> list = sqlSession.selectList("admin.selectCompanyList", new HashMap<>());
        list.forEach(row -> row.put("data_type", "company"));
        return list;
    }

    public Map<String, Object> getCompanyByCode(String companyCode) {
        return sqlSession.selectOne("admin.selectCompanyByCode", Map.of("company_code", companyCode));
    }

    @Transactional
    public Map<String, Object> createCompany(Map<String, Object> params) {
        sqlSession.insert("admin.insertCompany", params);
        return params;
    }

    @Transactional
    public Map<String, Object> updateCompanyInfo(String companyCode, Map<String, Object> params) {
        params.put("company_code", companyCode);
        sqlSession.update("admin.updateCompany", params);
        return params;
    }

    @Transactional
    public void deleteCompany(String companyCode) {
        sqlSession.delete("admin.deleteCompany", Map.of("company_code", companyCode));
    }

    // ── 테이블 스키마 ──────────────────────────────────────────────────────

    public List<Map<String, Object>> getTableSchema(String tableName) {
        return sqlSession.selectList("admin.selectTableSchema", Map.of("table_name", tableName));
    }
}
