package com.erp.service;

import com.erp.common.BaseService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
@Slf4j
public class ScreenEmbeddingService extends BaseService {

    private final ObjectMapper objectMapper;

    // ══════════════════════════════════════════════════════════════
    // Screen Embedding
    // ══════════════════════════════════════════════════════════════

    public List<Map<String, Object>> getScreenEmbeddingList(Map<String, Object> params) {
        List<Map<String, Object>> rows = sqlSession.selectList(
                "screenEmbedding.getScreenEmbeddingList", params);
        rows.forEach(r -> parseJsonFields(r, "config"));
        return rows;
    }

    public Map<String, Object> getScreenEmbeddingInfo(Map<String, Object> params) {
        Map<String, Object> row = sqlSession.selectOne(
                "screenEmbedding.getScreenEmbeddingInfo", params);
        if (row != null) parseJsonFields(row, "config");
        return row;
    }

    @Transactional
    public Map<String, Object> insertScreenEmbedding(Map<String, Object> params) {
        params.put("config", toJsonString(params.getOrDefault("config", new HashMap<>())));
        sqlSession.insert("screenEmbedding.insertScreenEmbedding", params);
        long id = toLong(params.get("id"));
        log.info("화면 임베딩 생성: id={}", id);
        Map<String, Object> result = sqlSession.selectOne(
                "screenEmbedding.getScreenEmbeddingInfo",
                Map.of("id", id, "company_code", params.get("company_code")));
        if (result != null) parseJsonFields(result, "config");
        return result;
    }

    @Transactional
    public Map<String, Object> updateScreenEmbedding(Map<String, Object> params) {
        if (params.get("config") != null) {
            params.put("config", toJsonString(params.get("config")));
        }
        int updated = sqlSession.update(
                "screenEmbedding.updateScreenEmbedding", params);
        if (updated == 0) return null;

        long id = toLong(params.get("id"));
        log.info("화면 임베딩 수정: id={}", id);
        Map<String, Object> result = sqlSession.selectOne(
                "screenEmbedding.getScreenEmbeddingInfo",
                Map.of("id", id, "company_code", params.get("company_code")));
        if (result != null) parseJsonFields(result, "config");
        return result;
    }

    @Transactional
    public boolean deleteScreenEmbedding(Map<String, Object> params) {
        int deleted = sqlSession.delete(
                "screenEmbedding.deleteScreenEmbedding", params);
        if (deleted == 0) return false;
        log.info("화면 임베딩 삭제: id={}", params.get("id"));
        return true;
    }

    // ══════════════════════════════════════════════════════════════
    // Screen Data Transfer
    // ══════════════════════════════════════════════════════════════

    public Map<String, Object> getScreenDataTransferInfo(Map<String, Object> params) {
        Map<String, Object> row = sqlSession.selectOne(
                "screenEmbedding.getScreenDataTransferInfo", params);
        if (row != null) parseJsonFields(row, "data_receivers", "button_config");
        return row;
    }

    @Transactional
    public Map<String, Object> insertScreenDataTransfer(Map<String, Object> params) {
        params.put("data_receivers", toJsonString(params.get("data_receivers")));
        params.put("button_config",  toJsonString(params.getOrDefault("button_config", new HashMap<>())));
        sqlSession.insert("screenEmbedding.insertScreenDataTransfer", params);
        long id = toLong(params.get("id"));
        log.info("데이터 전달 설정 생성: id={}", id);
        Map<String, Object> result = sqlSession.selectOne(
                "screenEmbedding.getScreenDataTransferInfoById",
                Map.of("id", id, "company_code", params.get("company_code")));
        if (result != null) parseJsonFields(result, "data_receivers", "button_config");
        return result;
    }

    @Transactional
    public Map<String, Object> updateScreenDataTransfer(Map<String, Object> params) {
        if (params.get("data_receivers") != null) {
            params.put("data_receivers", toJsonString(params.get("data_receivers")));
        }
        if (params.get("button_config") != null) {
            params.put("button_config", toJsonString(params.get("button_config")));
        }
        int updated = sqlSession.update(
                "screenEmbedding.updateScreenDataTransfer", params);
        if (updated == 0) return null;

        long id = toLong(params.get("id"));
        log.info("데이터 전달 설정 수정: id={}", id);
        Map<String, Object> result = sqlSession.selectOne(
                "screenEmbedding.getScreenDataTransferInfoById",
                Map.of("id", id, "company_code", params.get("company_code")));
        if (result != null) parseJsonFields(result, "data_receivers", "button_config");
        return result;
    }

    @Transactional
    public boolean deleteScreenDataTransfer(Map<String, Object> params) {
        int deleted = sqlSession.delete(
                "screenEmbedding.deleteScreenDataTransfer", params);
        if (deleted == 0) return false;
        log.info("데이터 전달 설정 삭제: id={}", params.get("id"));
        return true;
    }

    // ══════════════════════════════════════════════════════════════
    // Screen Split Panel
    // ══════════════════════════════════════════════════════════════

    public Map<String, Object> getScreenSplitPanelInfo(Map<String, Object> params) {
        Map<String, Object> row = sqlSession.selectOne(
                "screenEmbedding.getScreenSplitPanelInfo", params);
        if (row == null) return null;
        return buildSplitPanelResponse(row);
    }

    @Transactional
    public Map<String, Object> insertScreenSplitPanel(Map<String, Object> params) {
        String companyCode = (String) params.get("company_code");
        String userId      = (String) params.get("user_id");
        Object screenId    = params.get("screen_id");

        @SuppressWarnings("unchecked")
        Map<String, Object> leftEmb  = (Map<String, Object>) params.get("left_embedding");
        @SuppressWarnings("unchecked")
        Map<String, Object> rightEmb = (Map<String, Object>) params.get("right_embedding");
        @SuppressWarnings("unchecked")
        Map<String, Object> dt       = (Map<String, Object>) params.get("data_transfer");

        // 1. 좌측 임베딩 생성
        Map<String, Object> leftP = new HashMap<>();
        leftP.put("parent_screen_id", screenId);
        leftP.put("child_screen_id",  leftEmb.get("child_screen_id"));
        leftP.put("position",       leftEmb.get("position"));
        leftP.put("mode",           leftEmb.get("mode"));
        leftP.put("config",         toJsonString(leftEmb.getOrDefault("config", new HashMap<>())));
        leftP.put("company_code",    companyCode);
        leftP.put("user_id",         userId);
        sqlSession.insert("screenEmbedding.insertScreenEmbedding", leftP);
        long leftEmbeddingId = toLong(leftP.get("id"));

        // 2. 우측 임베딩 생성
        Map<String, Object> rightP = new HashMap<>();
        rightP.put("parent_screen_id", screenId);
        rightP.put("child_screen_id",  rightEmb.get("child_screen_id"));
        rightP.put("position",       rightEmb.get("position"));
        rightP.put("mode",           rightEmb.get("mode"));
        rightP.put("config",         toJsonString(rightEmb.getOrDefault("config", new HashMap<>())));
        rightP.put("company_code",    companyCode);
        rightP.put("user_id",         userId);
        sqlSession.insert("screenEmbedding.insertScreenEmbedding", rightP);
        long rightEmbeddingId = toLong(rightP.get("id"));

        // 3. 데이터 전달 설정 생성
        Map<String, Object> dtP = new HashMap<>();
        dtP.put("source_screen_id",      dt.get("source_screen_id"));
        dtP.put("target_screen_id",      dt.get("target_screen_id"));
        dtP.put("source_component_id",   dt.get("source_component_id"));
        dtP.put("source_component_type", dt.get("source_component_type"));
        dtP.put("data_receivers",       toJsonString(dt.get("data_receivers")));
        dtP.put("button_config",        toJsonString(dt.getOrDefault("button_config", new HashMap<>())));
        dtP.put("company_code",         companyCode);
        dtP.put("user_id",              userId);
        sqlSession.insert("screenEmbedding.insertScreenDataTransfer", dtP);
        long dataTransferId = toLong(dtP.get("id"));

        // 4. 분할 패널 생성
        Map<String, Object> spP = new HashMap<>();
        spP.put("screen_id",         screenId);
        spP.put("left_embedding_id",  leftEmbeddingId);
        spP.put("right_embedding_id", rightEmbeddingId);
        spP.put("data_transfer_id",   dataTransferId);
        spP.put("layout_config",     toJsonString(params.getOrDefault("layout_config", new HashMap<>())));
        spP.put("company_code",      companyCode);
        sqlSession.insert("screenEmbedding.insertScreenSplitPanel", spP);
        long splitPanelId = toLong(spP.get("id"));

        log.info("분할 패널 생성: id={}, screenId={}", splitPanelId, screenId);

        Map<String, Object> fetchP = new HashMap<>();
        fetchP.put("id",          splitPanelId);
        fetchP.put("company_code", companyCode);
        Map<String, Object> result = sqlSession.selectOne(
                "screenEmbedding.getScreenSplitPanelInfoById", fetchP);
        if (result != null) parseJsonFields(result, "layout_config");
        return result;
    }

    @Transactional
    public Map<String, Object> updateScreenSplitPanel(Map<String, Object> params) {
        params.put("layout_config", toJsonString(params.get("layout_config")));
        int updated = sqlSession.update(
                "screenEmbedding.updateScreenSplitPanel", params);
        if (updated == 0) return null;

        long id = toLong(params.get("id"));
        log.info("분할 패널 수정: id={}", id);
        Map<String, Object> fetchP = new HashMap<>();
        fetchP.put("id",          id);
        fetchP.put("company_code", params.get("company_code"));
        Map<String, Object> result = sqlSession.selectOne(
                "screenEmbedding.getScreenSplitPanelInfoById", fetchP);
        if (result != null) parseJsonFields(result, "layout_config");
        return result;
    }

    @Transactional
    public boolean deleteScreenSplitPanel(Map<String, Object> params) {
        // 1. 분할 패널 조회 (자식 ID 확인)
        Map<String, Object> panel = sqlSession.selectOne(
                "screenEmbedding.getScreenSplitPanelInfoById", params);
        if (panel == null) return false;

        String companyCode = (String) params.get("company_code");

        // 2. 분할 패널 삭제
        sqlSession.delete("screenEmbedding.deleteScreenSplitPanel", params);

        // 3. 관련 임베딩 및 데이터 전달 삭제
        Long leftId  = nullableLong(panel.get("left_embedding_id"));
        Long rightId = nullableLong(panel.get("right_embedding_id"));
        Long dtId    = nullableLong(panel.get("data_transfer_id"));

        if (leftId != null) {
            Map<String, Object> p = new HashMap<>();
            p.put("id", leftId);
            p.put("company_code", companyCode);
            sqlSession.delete("screenEmbedding.deleteScreenEmbedding", p);
        }
        if (rightId != null) {
            Map<String, Object> p = new HashMap<>();
            p.put("id", rightId);
            p.put("company_code", companyCode);
            sqlSession.delete("screenEmbedding.deleteScreenEmbedding", p);
        }
        if (dtId != null) {
            Map<String, Object> p = new HashMap<>();
            p.put("id", dtId);
            p.put("company_code", companyCode);
            sqlSession.delete("screenEmbedding.deleteScreenDataTransfer", p);
        }

        log.info("분할 패널 삭제 완료: id={}", params.get("id"));
        return true;
    }

    // ══════════════════════════════════════════════════════════════
    // private helpers
    // ══════════════════════════════════════════════════════════════

    /** flat JOIN row → Node 응답과 동일한 camelCase 구조로 변환 */
    private Map<String, Object> buildSplitPanelResponse(Map<String, Object> row) {
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("id",               row.get("id"));
        result.put("screen_id",         row.get("screen_id"));
        result.put("left_embedding_id",  row.get("left_embedding_id"));
        result.put("right_embedding_id", row.get("right_embedding_id"));
        result.put("data_transfer_id",   row.get("data_transfer_id"));
        result.put("layout_config",     parseJson(row.get("layout_config")));
        result.put("company_code",      row.get("company_code"));
        result.put("created_at",        row.get("created_at"));
        result.put("updated_at",        row.get("updated_at"));

        // 좌측 임베딩
        if (row.get("le_child_screen_id") != null) {
            Map<String, Object> le = new LinkedHashMap<>();
            le.put("id",            row.get("left_embedding_id"));
            le.put("parent_screen_id", row.get("le_parent_screen_id"));
            le.put("child_screen_id",  row.get("le_child_screen_id"));
            le.put("position",       row.get("le_position"));
            le.put("mode",           row.get("le_mode"));
            le.put("config",         parseJson(row.get("le_config")));
            result.put("left_embedding", le);
        } else {
            result.put("left_embedding", null);
        }

        // 우측 임베딩
        if (row.get("re_child_screen_id") != null) {
            Map<String, Object> re = new LinkedHashMap<>();
            re.put("id",            row.get("right_embedding_id"));
            re.put("parent_screen_id", row.get("re_parent_screen_id"));
            re.put("child_screen_id",  row.get("re_child_screen_id"));
            re.put("position",       row.get("re_position"));
            re.put("mode",           row.get("re_mode"));
            re.put("config",         parseJson(row.get("re_config")));
            result.put("right_embedding", re);
        } else {
            result.put("right_embedding", null);
        }

        // 데이터 전달
        if (row.get("source_screen_id") != null) {
            Map<String, Object> dt = new LinkedHashMap<>();
            dt.put("id",                 row.get("data_transfer_id"));
            dt.put("source_screen_id",     row.get("source_screen_id"));
            dt.put("target_screen_id",     row.get("target_screen_id"));
            dt.put("source_component_id",  row.get("source_component_id"));
            dt.put("source_component_type", row.get("source_component_type"));
            dt.put("data_receivers",      parseJson(row.get("data_receivers")));
            dt.put("button_config",       parseJson(row.get("button_config")));
            result.put("data_transfer", dt);
        } else {
            result.put("data_transfer", null);
        }

        return result;
    }

    /** JSON 컬럼(String/PGobject)을 Java 객체로 파싱 */
    private void parseJsonFields(Map<String, Object> row, String... fields) {
        for (String field : fields) {
            Object val = row.get(field);
            if (val != null) {
                row.put(field, parseJson(val));
            }
        }
    }

    private Object parseJson(Object val) {
        if (val == null) return null;
        if (!(val instanceof String)) return val;
        String s = (String) val;
        try {
            return objectMapper.readValue(s, Object.class);
        } catch (Exception e) {
            return val;  // 파싱 실패 시 원본 문자열 반환
        }
    }

    /** Java 객체 → JSON 문자열 직렬화 (DB JSONB 컬럼 삽입용) */
    private String toJsonString(Object obj) {
        if (obj == null) return "{}";
        if (obj instanceof String) return (String) obj;
        try {
            return objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            log.warn("JSON 직렬화 실패: {}", e.getMessage());
            return "{}";
        }
    }

    private long toLong(Object val) {
        if (val == null) return 0L;
        if (val instanceof Number) return ((Number) val).longValue();
        try { return Long.parseLong(val.toString()); } catch (NumberFormatException e) { return 0L; }
    }

    private Long nullableLong(Object val) {
        if (val == null) return null;
        if (val instanceof Number) return ((Number) val).longValue();
        try { return Long.parseLong(val.toString()); } catch (NumberFormatException e) { return null; }
    }
}
