package com.erp.service;

import com.erp.common.BaseService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class DigitalTwinService extends BaseService {

    @Autowired private CommonService commonService;
    @Autowired private ExternalDbConnectionService externalDbConnectionService;
    @Autowired private ObjectMapper objectMapper;

    // ──────────────────────────────────────────────────────────────────────────
    // 레이아웃 목록 조회
    // ──────────────────────────────────────────────────────────────────────────
    public Map<String, Object> getDigitalTwinLayoutList(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);
        commonService.applyPagination(params);
        Number cntNum = sqlSession.selectOne("digitalTwin.getDigitalTwinLayoutListCnt", params);
        int totalCount = cntNum != null ? cntNum.intValue() : 0;
        List<Map<String, Object>> list = sqlSession.selectList("digitalTwin.getDigitalTwinLayoutList", params);
        return commonService.buildListResponse(list, totalCount, params);
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 레이아웃 상세 조회 (객체 포함)
    // ──────────────────────────────────────────────────────────────────────────
    public Map<String, Object> getDigitalTwinLayoutInfo(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);
        Map<String, Object> layout = sqlSession.selectOne("digitalTwin.getDigitalTwinLayoutInfo", params);
        if (layout == null) return null;

        Map<String, Object> objectParams = new HashMap<>();
        objectParams.put("layout_id", params.get("id"));
        List<Map<String, Object>> objects = sqlSession.selectList("digitalTwin.getDigitalTwinObjectListByLayoutId", objectParams);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("layout", layout);
        result.put("objects", objects);
        return result;
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 레이아웃 생성 (객체 포함)
    // ──────────────────────────────────────────────────────────────────────────
    @Transactional
    public Map<String, Object> createDigitalTwinLayout(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);

        @SuppressWarnings("unchecked")
        List<Map<String, Object>> objects = (List<Map<String, Object>>) params.remove("objects");

        // hierarchyConfig: Map → JSON string
        Object hierarchyConfig = params.get("hierarchy_config");
        if (hierarchyConfig instanceof Map || hierarchyConfig instanceof List) {
            try {
                params.put("hierarchy_config", objectMapper.writeValueAsString(hierarchyConfig));
            } catch (Exception e) {
                log.warn("hierarchyConfig 직렬화 실패: {}", e.getMessage());
                params.put("hierarchy_config", null);
            }
        }

        sqlSession.insert("digitalTwin.insertDigitalTwinLayout", params);
        Long layoutId = toLong(params.get("id"));

        if (objects != null && !objects.isEmpty()) {
            saveObjects(layoutId, objects);
        }

        Map<String, Object> infoParams = new HashMap<>();
        infoParams.put("id", layoutId);
        infoParams.put("company_code", params.get("company_code"));
        return getDigitalTwinLayoutInfo(infoParams);
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 레이아웃 수정 (기존 객체 전체 교체)
    // ──────────────────────────────────────────────────────────────────────────
    @Transactional
    public Map<String, Object> updateDigitalTwinLayout(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);

        @SuppressWarnings("unchecked")
        List<Map<String, Object>> objects = (List<Map<String, Object>>) params.remove("objects");

        // hierarchyConfig: Map → JSON string
        Object hierarchyConfig = params.get("hierarchy_config");
        if (hierarchyConfig instanceof Map || hierarchyConfig instanceof List) {
            try {
                params.put("hierarchy_config", objectMapper.writeValueAsString(hierarchyConfig));
            } catch (Exception e) {
                log.warn("hierarchyConfig 직렬화 실패: {}", e.getMessage());
                params.put("hierarchy_config", null);
            }
        }

        int updated = sqlSession.update("digitalTwin.updateDigitalTwinLayout", params);
        if (updated == 0) return null;

        Long layoutId = toLong(params.get("id"));

        // 기존 객체 전체 삭제 후 재저장
        Map<String, Object> delParams = new HashMap<>();
        delParams.put("layout_id", layoutId);
        sqlSession.delete("digitalTwin.deleteDigitalTwinObjectsByLayoutId", delParams);

        if (objects != null && !objects.isEmpty()) {
            saveObjects(layoutId, objects);
        }

        Map<String, Object> infoParams = new HashMap<>();
        infoParams.put("id", layoutId);
        infoParams.put("company_code", params.get("company_code"));
        return getDigitalTwinLayoutInfo(infoParams);
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 레이아웃 삭제
    // ──────────────────────────────────────────────────────────────────────────
    @Transactional
    public boolean deleteDigitalTwinLayout(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);
        int deleted = sqlSession.delete("digitalTwin.deleteDigitalTwinLayout", params);
        return deleted > 0;
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 매핑 템플릿 목록
    // ──────────────────────────────────────────────────────────────────────────
    public Map<String, Object> getDigitalTwinTemplateList(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);
        commonService.applyPagination(params);
        Number cntNum = sqlSession.selectOne("digitalTwin.getDigitalTwinTemplateListCnt", params);
        int totalCount = cntNum != null ? cntNum.intValue() : 0;
        List<Map<String, Object>> list = sqlSession.selectList("digitalTwin.getDigitalTwinTemplateList", params);
        return commonService.buildListResponse(list, totalCount, params);
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 매핑 템플릿 상세
    // ──────────────────────────────────────────────────────────────────────────
    public Map<String, Object> getDigitalTwinTemplateInfo(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);
        return sqlSession.selectOne("digitalTwin.getDigitalTwinTemplateInfo", params);
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 매핑 템플릿 생성
    // ──────────────────────────────────────────────────────────────────────────
    @Transactional
    public Map<String, Object> createDigitalTwinTemplate(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);

        // config: Map → JSON string
        Object config = params.get("config");
        if (config instanceof Map || config instanceof List) {
            try {
                params.put("config", objectMapper.writeValueAsString(config));
            } catch (Exception e) {
                throw new IllegalArgumentException("config 직렬화 실패: " + e.getMessage());
            }
        }

        sqlSession.insert("digitalTwin.insertDigitalTwinTemplate", params);

        Map<String, Object> infoParams = new HashMap<>();
        infoParams.put("id", params.get("id"));
        infoParams.put("company_code", params.get("company_code"));
        return sqlSession.selectOne("digitalTwin.getDigitalTwinTemplateInfo", infoParams);
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 외부 DB - 전체 계층 구조 데이터 조회
    // ──────────────────────────────────────────────────────────────────────────
    @SuppressWarnings("unchecked")
    public Map<String, Object> getHierarchyData(Map<String, Object> body) {
        long connectionId = toLong(body.get("external_db_connection_id"));
        Object rawConfig = body.get("hierarchy_config");
        Map<String, Object> config = toMap(rawConfig);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("warehouse", null);
        result.put("levels", new ArrayList<>());
        result.put("materials", new ArrayList<>());

        // 창고 데이터
        Map<String, Object> whConfig = toMap(config.get("warehouse"));
        if (whConfig != null) {
            String tableName = str(whConfig, "table_name");
            List<Map<String, Object>> rows = queryExternal(connectionId,
                    "SELECT * FROM " + tableName + " LIMIT 100");
            result.put("warehouse", rows);
        }

        // 레벨 데이터
        Object levelsObj = config.get("levels");
        if (levelsObj instanceof List) {
            List<Map<String, Object>> levels = new ArrayList<>();
            for (Object levelObj : (List<?>) levelsObj) {
                Map<String, Object> level = toMap(levelObj);
                if (level == null) continue;
                String tableName = str(level, "table_name");
                List<Map<String, Object>> rows = queryExternal(connectionId,
                        "SELECT * FROM " + tableName + " LIMIT 1000");
                Map<String, Object> levelData = new LinkedHashMap<>();
                levelData.put("level", level.get("level"));
                levelData.put("name", level.get("name"));
                levelData.put("data", rows);
                levels.add(levelData);
            }
            result.put("levels", levels);
        }

        // 자재 데이터 (개수)
        Map<String, Object> matConfig = toMap(config.get("material"));
        if (matConfig != null) {
            String tableName = str(matConfig, "table_name");
            String locCol = str(matConfig, "location_key_column");
            List<Map<String, Object>> rows = queryExternal(connectionId,
                    "SELECT " + locCol + " AS location_key, COUNT(*) AS count" +
                    " FROM " + tableName +
                    " GROUP BY " + locCol);
            result.put("materials", rows);
        }

        return result;
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 외부 DB - 특정 부모의 하위 데이터 조회
    // ──────────────────────────────────────────────────────────────────────────
    @SuppressWarnings("unchecked")
    public List<Map<String, Object>> getChildrenData(Map<String, Object> body) {
        long connectionId = toLong(body.get("external_db_connection_id"));
        Map<String, Object> config = toMap(body.get("hierarchy_config"));
        int parentLevel = body.get("parent_level") instanceof Number
                ? ((Number) body.get("parent_level")).intValue()
                : Integer.parseInt(body.get("parent_level").toString());
        String parentKey = body.get("parent_key").toString();

        Object levelsObj = config.get("levels");
        if (!(levelsObj instanceof List)) return Collections.emptyList();

        Map<String, Object> nextLevel = null;
        for (Object levelObj : (List<?>) levelsObj) {
            Map<String, Object> level = toMap(levelObj);
            if (level == null) continue;
            Object lvlNum = level.get("level");
            int lvl = lvlNum instanceof Number ? ((Number) lvlNum).intValue()
                    : Integer.parseInt(lvlNum.toString());
            if (lvl == parentLevel + 1) {
                nextLevel = level;
                break;
            }
        }
        if (nextLevel == null) return Collections.emptyList();

        String tableName = str(nextLevel, "table_name");
        String parentKeyColumn = str(nextLevel, "parent_key_column");
        return queryExternal(connectionId,
                "SELECT * FROM " + tableName +
                " WHERE " + parentKeyColumn + " = '" + parentKey + "' LIMIT 1000");
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 외부 DB - 테이블 목록
    // ──────────────────────────────────────────────────────────────────────────
    public List<Map<String, Object>> getExternalTables(long connectionId) {
        return externalDbConnectionService.getTables(connectionId);
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 외부 DB - 테이블 미리보기 (10건)
    // ──────────────────────────────────────────────────────────────────────────
    public List<Map<String, Object>> getTablePreview(long connectionId, String tableName) {
        return queryExternal(connectionId, "SELECT * FROM " + tableName + " LIMIT 10");
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 외부 DB - 창고 목록 (레거시)
    // ──────────────────────────────────────────────────────────────────────────
    public List<Map<String, Object>> getWarehouses(long connectionId, String tableName) {
        return queryExternal(connectionId, "SELECT * FROM " + tableName + " LIMIT 100");
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 외부 DB - 구역 목록 (레거시)
    // ──────────────────────────────────────────────────────────────────────────
    public List<Map<String, Object>> getAreas(long connectionId, String tableName, String warehouseKey) {
        return queryExternal(connectionId,
                "SELECT * FROM " + tableName +
                " WHERE WAREKEY = '" + warehouseKey + "' LIMIT 1000");
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 외부 DB - 위치 목록 (레거시)
    // ──────────────────────────────────────────────────────────────────────────
    public List<Map<String, Object>> getLocations(long connectionId, String tableName, String areaKey) {
        return queryExternal(connectionId,
                "SELECT * FROM " + tableName +
                " WHERE AREAKEY = '" + areaKey + "' LIMIT 1000");
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 외부 DB - 자재 목록 (레거시)
    // ──────────────────────────────────────────────────────────────────────────
    public List<Map<String, Object>> getMaterials(long connectionId, String tableName,
            String locaKey, String locationKeyColumn, String layerColumn) {
        String orderBy = (layerColumn != null && !layerColumn.isBlank())
                ? " ORDER BY " + layerColumn : "";
        return queryExternal(connectionId,
                "SELECT * FROM " + tableName +
                " WHERE " + locationKeyColumn + " = '" + locaKey + "'" +
                orderBy + " LIMIT 1000");
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 외부 DB - 자재 개수 (여러 Location 일괄, 레거시)
    // ──────────────────────────────────────────────────────────────────────────
    @SuppressWarnings("unchecked")
    public List<Map<String, Object>> getMaterialCounts(Map<String, Object> body) {
        long connectionId = toLong(body.get("external_db_connection_id"));
        String tableName = str(body, "table_name");
        List<String> locationKeys = (List<String>) body.get("location_keys");

        String keysString = locationKeys.stream()
                .map(k -> "'" + k + "'")
                .collect(Collectors.joining(","));

        return queryExternal(connectionId,
                "SELECT LOCAKEY AS location_key, COUNT(*) AS count" +
                " FROM " + tableName +
                " WHERE LOCAKEY IN (" + keysString + ")" +
                " GROUP BY LOCAKEY");
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 헬퍼: 외부 DB 쿼리 실행 → rows 반환
    // ──────────────────────────────────────────────────────────────────────────
    @SuppressWarnings("unchecked")
    private List<Map<String, Object>> queryExternal(long connectionId, String sql) {
        Map<String, Object> result = externalDbConnectionService.executeQuery(connectionId, sql);
        Object data = result.get("data");
        if (data instanceof List) return (List<Map<String, Object>>) data;
        return Collections.emptyList();
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 헬퍼: 객체 저장 (부모 → 자식 순서로 2단계 저장)
    // ──────────────────────────────────────────────────────────────────────────
    private void saveObjects(Long layoutId, List<Map<String, Object>> objects) {
        Map<Object, Long> idMapping = new HashMap<>();

        // 1단계: parentId 없는 부모 객체 먼저 저장
        for (Map<String, Object> obj : objects) {
            if (obj.get("parent_id") == null) {
                Map<String, Object> p = buildObjectParams(layoutId, obj, null);
                sqlSession.insert("digitalTwin.insertDigitalTwinObject", p);
                Long dbId = toLong(p.get("id"));
                if (obj.get("id") != null) {
                    idMapping.put(obj.get("id"), dbId);
                }
            }
        }

        // 2단계: parentId 있는 자식 객체 저장 (매핑된 실제 ID 사용)
        for (Map<String, Object> obj : objects) {
            if (obj.get("parent_id") != null) {
                Long realParentId = idMapping.get(obj.get("parent_id"));
                Map<String, Object> p = buildObjectParams(layoutId, obj, realParentId);
                sqlSession.insert("digitalTwin.insertDigitalTwinObject", p);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> buildObjectParams(Long layoutId, Map<String, Object> obj, Long parentDbId) {
        Map<String, Object> p = new HashMap<>();
        p.put("layout_id", layoutId);
        p.put("type", obj.get("type"));
        p.put("name", obj.get("name"));

        Map<String, Object> position = toMap(obj.get("position"));
        p.put("pos_x", position != null ? position.get("x") : null);
        p.put("pos_y", position != null ? position.get("y") : null);
        p.put("pos_z", position != null ? position.get("z") : null);

        Map<String, Object> size = toMap(obj.get("size"));
        p.put("size_x", size != null ? size.get("x") : null);
        p.put("size_y", size != null ? size.get("y") : null);
        p.put("size_z", size != null ? size.get("z") : null);

        p.put("rotation", obj.get("rotation"));
        p.put("color", obj.get("color"));
        p.put("area_key", obj.get("area_key"));
        p.put("loca_key", obj.get("loca_key"));
        p.put("loc_type", obj.get("loc_type"));
        p.put("material_count", obj.get("material_count"));

        Map<String, Object> materialPreview = toMap(obj.get("material_preview"));
        p.put("material_preview_height", materialPreview != null ? materialPreview.get("height") : null);

        p.put("parent_db_id", parentDbId);
        p.put("display_order", obj.get("display_order"));
        p.put("locked", obj.get("locked"));
        p.put("hierarchy_level", obj.get("hierarchy_level"));
        p.put("parent_key", obj.get("parent_key"));
        p.put("external_key", obj.get("external_key"));
        return p;
    }

    // ──────────────────────────────────────────────────────────────────────────
    // 유틸리티
    // ──────────────────────────────────────────────────────────────────────────
    private String str(Map<String, Object> m, String key) {
        Object v = m.get(key);
        return v != null ? v.toString() : null;
    }

    private Long toLong(Object v) {
        if (v == null) return null;
        if (v instanceof Long) return (Long) v;
        if (v instanceof Number) return ((Number) v).longValue();
        return Long.parseLong(v.toString());
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> toMap(Object obj) {
        if (obj instanceof Map) return (Map<String, Object>) obj;
        if (obj instanceof String) {
            try {
                return objectMapper.readValue((String) obj,
                        new TypeReference<Map<String, Object>>() {});
            } catch (Exception e) {
                log.warn("JSON 파싱 실패: {}", e.getMessage());
                return null;
            }
        }
        return null;
    }
}
