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 java.util.*;

@Service
@Slf4j
public class MetaService extends BaseService {

    @Autowired
    private CommonService commonService;

    @Autowired
    private ObjectMapper objectMapper;

    private static final String NS = "meta.";

    private static final Set<String> SYSTEM_FIELDS = Set.of(
        "company_code", "created_by", "created_date", "updated_by", "updated_date",
        "is_active", "deleted_date", "deleted_by", "writer", "write_date"
    );

    private static final Set<String> SEARCHABLE_TYPES = Set.of(
        "text", "select", "entity", "date", "code"
    );

    // ──────────────────────────────────────────────────
    // 테이블 목록
    // ──────────────────────────────────────────────────

    public List<Map<String, Object>> getMetaTableList(Map<String, Object> params) {
        return sqlSession.selectList(NS + "getMetaTableList", params);
    }

    // ──────────────────────────────────────────────────
    // FieldConfig[] 반환
    // ──────────────────────────────────────────────────

    public Map<String, Object> getMetaFields(Map<String, Object> params) {
        String tableName = (String) params.get("table_name");

        List<Map<String, Object>> schemaCols = sqlSession.selectList(NS + "getSchemaColumns", params);
        List<String> pks = sqlSession.selectList(NS + "getPrimaryKeys", params);
        List<Map<String, Object>> customMeta = sqlSession.selectList(NS + "getCustomMeta", params);

        String tableLabel = sqlSession.selectOne(NS + "getTableLabel", params);
        if (tableLabel == null || tableLabel.isBlank()) {
            tableLabel = tableName;
        }

        String primaryKey = pks.isEmpty() ? null : String.join(",", pks);
        List<Map<String, Object>> fields = buildFieldConfigs(schemaCols, pks, customMeta);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("table_name", tableName);
        result.put("table_label", tableLabel);
        result.put("primary_key", primaryKey);
        result.put("fields", fields);
        return result;
    }

    // ──────────────────────────────────────────────────
    // FieldConfig 빌드 (핵심)
    // ──────────────────────────────────────────────────

    private List<Map<String, Object>> buildFieldConfigs(
        List<Map<String, Object>> schemaCols,
        List<String> pks,
        List<Map<String, Object>> customMeta
    ) {
        Set<String> pkSet = new HashSet<>(pks);

        Map<String, Map<String, Object>> metaMap = new LinkedHashMap<>();
        for (Map<String, Object> meta : customMeta) {
            String colName = str(meta, "column_name");
            if (colName != null) {
                metaMap.put(colName, meta);
            }
        }

        List<Map<String, Object>> fields = new ArrayList<>();

        for (Map<String, Object> schemaCol : schemaCols) {
            String columnName = str(schemaCol, "column_name");
            String dataType = str(schemaCol, "data_type");
            String isNullable = str(schemaCol, "is_nullable");
            String columnDefault = str(schemaCol, "column_default");
            int ordinalPosition = num(schemaCol, "ordinal_position");

            Map<String, Object> meta = metaMap.get(columnName);
            Map<String, Object> detailSettings = meta != null ? parseDetailSettings(meta) : null;

            boolean isPk = pkSet.contains(columnName);
            boolean isSystem = SYSTEM_FIELDS.contains(columnName);

            // ── type ──
            String fieldType;
            if (meta != null && str(meta, "input_type") != null && !str(meta, "input_type").isBlank()) {
                fieldType = mapInputTypeToFieldType(str(meta, "input_type"));
            } else {
                fieldType = mapDataTypeToFieldType(dataType);
            }

            // ── label ──
            String label = columnName;
            if (meta != null && str(meta, "column_label") != null && !str(meta, "column_label").isBlank()) {
                label = str(meta, "column_label");
            }

            // ── order ──
            int order = ordinalPosition;
            if (meta != null && meta.get("display_order") != null) {
                int displayOrder = num(meta, "display_order");
                if (displayOrder > 0) order = displayOrder;
            }

            // ── visible ──
            boolean visible = !isSystem;
            if (!isSystem && meta != null && meta.get("is_visible") != null) {
                String isVisible = meta.get("is_visible").toString();
                visible = "Y".equalsIgnoreCase(isVisible) || "true".equalsIgnoreCase(isVisible);
            }

            // ── required (★ 앱 레벨 메타 우선, DB 스키마 폴백) ──
            boolean required;
            if (!isSystem && meta != null && meta.get("is_nullable") != null) {
                // table_type_columns.IS_NULLABLE가 있으면 앱 레벨 메타가 진실
                String metaNullable = meta.get("is_nullable").toString();
                required = "NO".equalsIgnoreCase(metaNullable);
            } else {
                // 없으면 information_schema 폴백
                required = "NO".equalsIgnoreCase(isNullable) && columnDefault == null && !isSystem;
            }

            // ── editable ──
            boolean editable = !isPk && !isSystem;
            if ("code".equals(fieldType)) editable = false;

            // ── searchable ──
            boolean searchable = !isSystem && SEARCHABLE_TYPES.contains(fieldType);

            // ── build field map ──
            Map<String, Object> field = new LinkedHashMap<>();
            field.put("column", columnName);
            field.put("label", label);
            field.put("type", fieldType);
            field.put("visible", visible);
            field.put("order", order);
            field.put("width", getDefaultWidth(fieldType));
            field.put("align", "number".equals(fieldType) ? "right" : "left");
            field.put("required", required);
            field.put("editable", editable);
            field.put("pk", isPk);
            field.put("system", isSystem);
            field.put("searchable", searchable);
            field.put("sortable", !isSystem);
            field.put("format", getDefaultFormat(fieldType));
            field.put("options", null);
            field.put("ref", null);
            field.put("computed", null);

            // ── entity → ref ──
            if ("entity".equals(fieldType) && meta != null) {
                Map<String, Object> ref = buildFieldRef(meta, detailSettings);
                if (ref != null) field.put("ref", ref);
            }

            // ── select → options ──
            if ("select".equals(fieldType) && detailSettings != null) {
                List<Object> options = extractOptions(detailSettings);
                if (options != null && !options.isEmpty()) field.put("options", options);
            }

            // ── computed ──
            if (detailSettings != null && detailSettings.containsKey("computed")) {
                String computed = detailSettings.get("computed").toString();
                if (!computed.isBlank()) {
                    field.put("computed", computed);
                    field.put("editable", false);
                }
            }

            fields.add(field);
        }

        fields.sort(Comparator.comparingInt(f -> num(f, "order")));
        return fields;
    }

    // ──────────────────────────────────────────────────
    // 타입 매핑
    // ──────────────────────────────────────────────────

    private String mapDataTypeToFieldType(String dataType) {
        if (dataType == null) return "text";
        return switch (dataType.toLowerCase()) {
            case "character varying", "varchar" -> "text";
            case "text" -> "textarea";
            case "integer", "bigint", "smallint" -> "number";
            case "numeric", "decimal", "real", "double precision" -> "number";
            case "boolean" -> "checkbox";
            case "date" -> "date";
            case "timestamp without time zone", "timestamp with time zone" -> "datetime";
            case "jsonb", "json" -> "textarea";
            case "bytea" -> "file";
            default -> "text";
        };
    }

    private String mapInputTypeToFieldType(String inputType) {
        if (inputType == null) return "text";
        return switch (inputType.toLowerCase()) {
            case "text" -> "text";
            case "number", "decimal" -> "number";
            case "date" -> "date";
            case "datetime" -> "datetime";
            case "select", "category" -> "select";
            case "entity" -> "entity";
            case "checkbox", "boolean" -> "checkbox";
            case "textarea", "text_area" -> "textarea";
            case "file" -> "file";
            case "code", "numbering" -> "code";
            case "email", "password", "tel" -> "text";
            default -> "text";
        };
    }

    // ──────────────────────────────────────────────────
    // entity ref 빌드
    // ──────────────────────────────────────────────────

    private Map<String, Object> buildFieldRef(Map<String, Object> meta, Map<String, Object> detailSettings) {
        // 1차: top-level 컬럼
        String refTable = str(meta, "reference_table");
        String refColumn = str(meta, "reference_column");
        String displayCol = str(meta, "display_column");

        // 2차: detail_settings JSON 폴백
        if ((refTable == null || refTable.isBlank()) && detailSettings != null) {
            refTable = strFromMap(detailSettings, "referenceTable");
        }
        if ((refColumn == null || refColumn.isBlank()) && detailSettings != null) {
            refColumn = strFromMap(detailSettings, "referenceColumn");
        }
        if ((displayCol == null || displayCol.isBlank()) && detailSettings != null) {
            displayCol = strFromMap(detailSettings, "displayColumn");
        }

        if (refTable == null || refTable.isBlank()) return null;

        Map<String, Object> ref = new LinkedHashMap<>();
        ref.put("table", refTable);
        ref.put("value_column", refColumn != null && !refColumn.isBlank() ? refColumn : "id");
        ref.put("display_column", displayCol != null && !displayCol.isBlank() ? displayCol : refColumn);

        // search_columns
        if (detailSettings != null && detailSettings.containsKey("searchColumns")) {
            Object sc = detailSettings.get("searchColumns");
            if (sc instanceof List) {
                ref.put("search_columns", sc);
            }
        }

        return ref;
    }

    // ──────────────────────────────────────────────────
    // select options 추출
    // ──────────────────────────────────────────────────

    /**
     * select options 추출.
     * ★ FieldOption 규격: string이면 value=label, 객체면 {value, label} 그대로 보존
     * - string[]: ["임시저장", "확정"] → 그대로 반환
     * - [{value,label}]: [{value:"DIRECT",label:"직접배송"}] → 그대로 반환
     * - 프론트에서 select 렌더러는 항상 value를 저장/전송, label을 표시
     */
    @SuppressWarnings("unchecked")
    private List<Object> extractOptions(Map<String, Object> detailSettings) {
        if (detailSettings == null || !detailSettings.containsKey("options")) return null;
        Object optionsObj = detailSettings.get("options");
        if (!(optionsObj instanceof List)) return null;

        List<?> optionsList = (List<?>) optionsObj;
        if (optionsList.isEmpty()) return null;

        List<Object> result = new ArrayList<>();
        for (Object item : optionsList) {
            if (item instanceof String) {
                // 단순 문자열: value=label로 해석
                result.add(item);
            } else if (item instanceof Map) {
                // {value, label} 객체: 그대로 보존
                Map<String, Object> optMap = (Map<String, Object>) item;
                Map<String, Object> opt = new LinkedHashMap<>();
                Object value = optMap.get("value");
                Object label = optMap.get("label");
                opt.put("value", value != null ? value.toString() : "");
                opt.put("label", label != null ? label.toString() : (value != null ? value.toString() : ""));
                result.add(opt);
            }
        }
        return result.isEmpty() ? null : result;
    }

    // ──────────────────────────────────────────────────
    // detail_settings 파싱
    // ──────────────────────────────────────────────────

    private Map<String, Object> parseDetailSettings(Map<String, Object> meta) {
        Object detailObj = meta.get("detail_settings");
        if (detailObj == null) return null;

        String detailStr = detailObj.toString().trim();
        if (detailStr.isEmpty() || "{}".equals(detailStr)) return null;

        try {
            return objectMapper.readValue(detailStr, new TypeReference<Map<String, Object>>() {});
        } catch (Exception e) {
            log.warn("detail_settings 파싱 실패: {}", detailStr);
            return null;
        }
    }

    // ──────────────────────────────────────────────────
    // 기본값
    // ──────────────────────────────────────────────────

    private int getDefaultWidth(String fieldType) {
        return switch (fieldType) {
            case "number" -> 100;
            case "date" -> 120;
            case "datetime" -> 160;
            case "select" -> 130;
            case "entity" -> 180;
            case "checkbox" -> 80;
            case "code" -> 120;
            case "textarea" -> 200;
            default -> 150;
        };
    }

    private String getDefaultFormat(String fieldType) {
        return switch (fieldType) {
            case "number" -> "#,##0";
            case "date" -> "YYYY-MM-DD";
            case "datetime" -> "YYYY-MM-DD HH:mm";
            default -> null;
        };
    }

    // ──────────────────────────────────────────────────
    // 유틸
    // ──────────────────────────────────────────────────

    private String str(Map<String, Object> map, String key) {
        Object val = map.get(key);
        return val != null ? val.toString() : null;
    }

    private String strFromMap(Map<String, Object> map, String key) {
        Object val = map.get(key);
        return val != null ? val.toString() : null;
    }

    private int num(Map<String, Object> map, String key) {
        Object val = map.get(key);
        if (val instanceof Number) return ((Number) val).intValue();
        if (val != null) {
            try { return Integer.parseInt(val.toString()); }
            catch (NumberFormatException e) { return 0; }
        }
        return 0;
    }

    // ──────────────────────────────────────────────────
    // 테이블 간 업무 관계 (Phase 5 — 제어 모드)
    // ──────────────────────────────────────────────────

    public List<Map<String, Object>> getMetaRelations(Map<String, Object> params) {
        return sqlSession.selectList(NS + "getMetaRelations", params);
    }
}
