package com.erp.service;

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

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

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

    private final ObjectMapper objectMapper;

    private static final String NS = "entitySearch.";

    private String sanitize(String name) {
        if (name == null) return "";
        return name.replaceAll("[^a-zA-Z0-9_]", "");
    }

    private int parseIntOrDefault(Object v, int defaultVal) {
        if (v == null) return defaultVal;
        try { return Integer.parseInt(v.toString()); } catch (NumberFormatException e) { return defaultVal; }
    }

    private Set<String> fetchColumns(String tableName) {
        Map<String, Object> p = new HashMap<>();
        p.put("table_name", tableName);
        List<Map<String, Object>> rows = sqlSession.selectList(NS + "getTableColumnList", p);
        return rows.stream()
                .map(r -> (String) r.get("column_name"))
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
    }

    private void applyFilters(String filtersJson, Set<String> existingColumns,
                               StringBuilder whereClause, Map<String, Object> params,
                               AtomicInteger idx, String tableName) {
        if (filtersJson == null || filtersJson.isBlank()) return;

        List<?> filters;
        try {
            filters = objectMapper.readValue(filtersJson, List.class);
        } catch (Exception e) {
            log.warn("filters JSON 파싱 실패 table={}", tableName);
            return;
        }
        if (!(filters instanceof List)) return;

        for (Object raw : filters) {
            if (!(raw instanceof Map)) continue;
            @SuppressWarnings("unchecked")
            Map<String, Object> filter = (Map<String, Object>) raw;

            String column   = (String) filter.get("column");
            String operator = (String) filter.getOrDefault("operator", "=");
            Object value    = filter.get("value");

            if (column == null || !existingColumns.contains(column)) {
                log.warn("필터 컬럼 미존재 제외 table={} column={}", tableName, column);
                continue;
            }
            String safeCol = sanitize(column);
            applyOneCondition(safeCol, operator, value, whereClause, params, idx);
        }
    }

    private void applyFilterCondition(String filterConditionJson, Set<String> existingColumns,
                                       StringBuilder whereClause, Map<String, Object> params,
                                       AtomicInteger idx, String tableName) {
        if (filterConditionJson == null || filterConditionJson.isBlank()
                || "{}".equals(filterConditionJson.trim())) return;

        Map<String, Object> filterMap;
        try {
            filterMap = objectMapper.readValue(filterConditionJson, Map.class);
        } catch (Exception e) {
            log.warn("filterCondition JSON 파싱 실패 table={}", tableName);
            return;
        }

        for (Map.Entry<String, Object> entry : filterMap.entrySet()) {
            String key      = entry.getKey();
            Object value    = entry.getValue();
            String column   = key;
            String operator = "=";

            if (key.contains("__")) {
                String[] parts = key.split("__", 2);
                column   = parts[0];
                operator = parts[1];
            }

            if (!existingColumns.contains(column)) {
                log.warn("엔티티 검색: 필터 조건에 존재하지 않는 컬럼 제외 table={} column={}", tableName, column);
                continue;
            }
            String safeCol = sanitize(column);
            applyOneCondition(safeCol, operator, value, whereClause, params, idx);
        }
    }

    @SuppressWarnings("unchecked")
    private void applyOneCondition(String safeCol, String operator, Object value,
                                    StringBuilder whereClause, Map<String, Object> params,
                                    AtomicInteger idx) {
        switch (operator) {
            case "=" -> {
                int i = idx.getAndIncrement();
                whereClause.append(" AND \"").append(safeCol).append("\" = #{condVal_").append(i).append("}");
                params.put("condVal_" + i, value);
            }
            case "!=" -> {
                int i = idx.getAndIncrement();
                whereClause.append(" AND \"").append(safeCol).append("\" != #{condVal_").append(i).append("}");
                params.put("condVal_" + i, value);
            }
            case ">", "<", ">=", "<=" -> {
                int i = idx.getAndIncrement();
                whereClause.append(" AND \"").append(safeCol).append("\" ")
                           .append(operator).append(" #{condVal_").append(i).append("}");
                params.put("condVal_" + i, value);
            }
            case "in" -> {
                List<String> vals = toList(value);
                if (!vals.isEmpty()) {
                    whereClause.append(" AND \"").append(safeCol).append("\" IN (");
                    for (int j = 0; j < vals.size(); j++) {
                        if (j > 0) whereClause.append(", ");
                        int i = idx.getAndIncrement();
                        whereClause.append("#{condVal_").append(i).append("}");
                        params.put("condVal_" + i, vals.get(j));
                    }
                    whereClause.append(")");
                }
            }
            case "not_in" -> {
                List<String> vals = toList(value);
                if (!vals.isEmpty()) {
                    whereClause.append(" AND \"").append(safeCol).append("\" NOT IN (");
                    for (int j = 0; j < vals.size(); j++) {
                        if (j > 0) whereClause.append(", ");
                        int i = idx.getAndIncrement();
                        whereClause.append("#{condVal_").append(i).append("}");
                        params.put("condVal_" + i, vals.get(j));
                    }
                    whereClause.append(")");
                }
            }
            case "like" -> {
                int i = idx.getAndIncrement();
                whereClause.append(" AND \"").append(safeCol).append("\"::text ILIKE #{condVal_").append(i).append("}");
                params.put("condVal_" + i, "%" + value + "%");
            }
            case "is_null"    -> whereClause.append(" AND \"").append(safeCol).append("\" IS NULL");
            case "is_not_null" -> whereClause.append(" AND \"").append(safeCol).append("\" IS NOT NULL");
            default -> {
                int i = idx.getAndIncrement();
                whereClause.append(" AND \"").append(safeCol).append("\" = #{condVal_").append(i).append("}");
                params.put("condVal_" + i, value);
            }
        }
    }

    private List<String> toList(Object value) {
        if (value instanceof List<?> list) {
            return list.stream().map(Object::toString).collect(Collectors.toList());
        }
        if (value instanceof String s) {
            return Arrays.stream(s.split(",")).map(String::trim)
                         .filter(v -> !v.isEmpty()).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }

    public Map<String, Object> searchEntity(Map<String, Object> params) {
        String tableName = sanitize((String) params.get("table_name"));
        if (tableName.isEmpty()) {
            throw new IllegalArgumentException("테이블명이 지정되지 않았습니다.");
        }

        String companyCode = (String) params.get("company_code");
        Set<String> existingColumns = fetchColumns(tableName);

        StringBuilder whereClause = new StringBuilder();
        Map<String, Object> qp = new HashMap<>();
        AtomicInteger idx = new AtomicInteger(0);

        if (!"*".equals(companyCode) && existingColumns.contains("company_code")) {
            int i = idx.getAndIncrement();
            whereClause.append(" AND company_code = #{condVal_").append(i).append("}");
            qp.put("condVal_" + i, companyCode);
        }

        String searchText   = (String) params.getOrDefault("search_text", "");
        String searchFields = (String) params.getOrDefault("search_fields", "");
        if (searchText != null && !searchText.isBlank()) {
            List<String> fields = Arrays.stream(searchFields.split(","))
                    .map(String::trim).filter(f -> !f.isEmpty() && existingColumns.contains(f))
                    .collect(Collectors.toList());

            if (fields.isEmpty()) {
                List<String> defaults = List.of(
                    "name", "code", "description", "title", "label",
                    "item_name", "item_code", "item_number",
                    "equipment_name", "equipment_code",
                    "inspection_item", "consumable_name",
                    "supplier_name", "customer_name", "product_name"
                );
                fields = defaults.stream().filter(existingColumns::contains).collect(Collectors.toList());
            }

            if (!fields.isEmpty()) {
                whereClause.append(" AND (");
                for (int j = 0; j < fields.size(); j++) {
                    if (j > 0) whereClause.append(" OR ");
                    int i = idx.getAndIncrement();
                    whereClause.append(sanitize(fields.get(j))).append("::text ILIKE #{condVal_").append(i).append("}");
                    qp.put("condVal_" + i, "%" + searchText + "%");
                }
                whereClause.append(")");
            }
        }

        String filterCondition = (String) params.getOrDefault("filter_condition", "{}");
        applyFilterCondition(filterCondition, existingColumns, whereClause, qp, idx, tableName);

        String orderByColumn = "1";
        if (existingColumns.contains("id")) {
            orderByColumn = "\"id\"";
        } else {
            try {
                Map<String, Object> pkp = new HashMap<>();
                pkp.put("table_name", tableName);
                Map<String, Object> pk = sqlSession.selectOne(NS + "getPrimaryKeyInfo", pkp);
                if (pk != null && pk.get("column_name") != null) {
                    orderByColumn = "\"" + sanitize((String) pk.get("column_name")) + "\"";
                }
            } catch (Exception e) {
                log.warn("PK 조회 실패 table={}", tableName);
            }
        }

        int page   = parseIntOrDefault(params.get("page"),  1);
        int limit  = parseIntOrDefault(params.get("limit"), 20);
        int offset = (page - 1) * limit;

        qp.put("table_name",     tableName);
        qp.put("where_clause",   whereClause.toString());
        qp.put("order_by_column", orderByColumn);
        qp.put("page_limit",     limit);
        qp.put("page_offset",    offset);

        List<Map<String, Object>> data = sqlSession.selectList(NS + "getEntitySearchList", qp);
        Integer totalObj = sqlSession.selectOne(NS + "getEntitySearchListCnt", qp);
        int total = totalObj != null ? totalObj : 0;

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("data", data);
        Map<String, Object> pagination = new LinkedHashMap<>();
        pagination.put("total", total);
        pagination.put("page",  page);
        pagination.put("limit", limit);
        result.put("pagination", pagination);
        return result;
    }

    public List<Map<String, Object>> getEntityOptions(Map<String, Object> params) {
        String tableName = sanitize((String) params.get("table_name"));
        if (tableName.isEmpty()) {
            throw new IllegalArgumentException("테이블명이 지정되지 않았습니다.");
        }

        String companyCode    = (String) params.get("company_code");
        String requestedValue = sanitize((String) params.getOrDefault("value", "id"));
        String requestedLabel = sanitize((String) params.getOrDefault("label", "name"));
        String fieldsParam    = (String) params.get("fields");
        String filtersJson    = (String) params.get("filters");

        Set<String> existingColumns = fetchColumns(tableName);

        String valueCol = existingColumns.contains(requestedValue) ? requestedValue : "id";
        if (!existingColumns.contains(valueCol)) {
            throw new IllegalArgumentException(
                "테이블 \"" + tableName + "\"에 값 컬럼 \"" + requestedValue + "\"이 존재하지 않습니다.");
        }

        String labelCandidate = existingColumns.contains(requestedLabel) ? requestedLabel : "name";
        String labelCol = existingColumns.contains(labelCandidate) ? labelCandidate : valueCol;

        String extraColsExpr = "";
        if (fieldsParam != null && !fieldsParam.isBlank()) {
            List<String> extras = Arrays.stream(fieldsParam.split(","))
                    .map(f -> sanitize(f.trim()))
                    .filter(f -> !f.isEmpty() && existingColumns.contains(f)
                                 && !f.equals(valueCol) && !f.equals(labelCol))
                    .collect(Collectors.toList());
            if (!extras.isEmpty()) {
                extraColsExpr = ", " + extras.stream()
                        .map(f -> "\"" + f + "\"")
                        .collect(Collectors.joining(", "));
            }
        }

        StringBuilder whereClause = new StringBuilder();
        Map<String, Object> qp   = new HashMap<>();
        AtomicInteger idx         = new AtomicInteger(0);

        if (!"*".equals(companyCode) && existingColumns.contains("company_code")) {
            int i = idx.getAndIncrement();
            whereClause.append(" AND company_code = #{condVal_").append(i).append("}");
            qp.put("condVal_" + i, companyCode);
        }

        applyFilters(filtersJson, existingColumns, whereClause, qp, idx, tableName);

        qp.put("table_name",     tableName);
        qp.put("select_columns", "\"" + valueCol + "\" AS value, \"" + labelCol + "\" AS label" + extraColsExpr);
        qp.put("where_clause",   whereClause.toString());
        qp.put("label_column",   "\"" + labelCol + "\"");

        return sqlSession.selectList(NS + "getEntityOptionList", qp);
    }

    public List<Map<String, Object>> getDistinctColumnValues(Map<String, Object> params) {
        String tableName  = sanitize((String) params.get("table_name"));
        String columnName = sanitize((String) params.get("column_name"));

        if (tableName.isEmpty()) throw new IllegalArgumentException("테이블명이 지정되지 않았습니다.");
        if (columnName.isEmpty()) throw new IllegalArgumentException("컬럼명이 지정되지 않았습니다.");

        String companyCode = (String) params.get("company_code");
        String labelColReq = sanitize((String) params.getOrDefault("label_column", ""));
        String filtersJson = (String) params.get("filters");

        Set<String> existingColumns = fetchColumns(tableName);

        if (!existingColumns.contains(columnName)) {
            throw new IllegalArgumentException(
                "테이블 \"" + tableName + "\"에 컬럼 \"" + columnName + "\"이 존재하지 않습니다.");
        }

        String effectiveLabelCol = (!labelColReq.isEmpty() && existingColumns.contains(labelColReq))
                ? labelColReq : columnName;

        StringBuilder whereClause = new StringBuilder();
        Map<String, Object> qp   = new HashMap<>();
        AtomicInteger idx         = new AtomicInteger(0);

        if (!"*".equals(companyCode) && existingColumns.contains("company_code")) {
            int i = idx.getAndIncrement();
            whereClause.append(" AND company_code = #{condVal_").append(i).append("}");
            qp.put("condVal_" + i, companyCode);
        }

        applyFilters(filtersJson, existingColumns, whereClause, qp, idx, tableName);

        qp.put("table_name",   tableName);
        qp.put("value_column", "\"" + columnName + "\"");
        qp.put("label_column", "\"" + effectiveLabelCol + "\"");
        qp.put("where_clause", whereClause.toString());

        List<Map<String, Object>> rows = sqlSession.selectList(NS + "getDistinctValueList", qp);

        if (rows.isEmpty()) return rows;

        List<String> rawValues = rows.stream()
                .map(r -> r.get("value")).filter(Objects::nonNull)
                .map(Object::toString).collect(Collectors.toList());

        Map<String, String> labelMap = new HashMap<>();

        try {
            Map<String, Object> cvp = new HashMap<>();
            cvp.put("table_name",   tableName);
            cvp.put("column_name",  columnName);
            cvp.put("raw_values",   rawValues);
            cvp.put("company_code", companyCode);
            List<Map<String, Object>> cvRows = sqlSession.selectList(NS + "getCategoryValueList", cvp);
            for (Map<String, Object> r : cvRows) {
                labelMap.put((String) r.get("value_code"), (String) r.get("value_label"));
            }
        } catch (Exception e) {
            log.debug("category_values 조회 실패 (무시): {}", e.getMessage());
        }

        try {
            Map<String, Object> ttcp = new HashMap<>();
            ttcp.put("table_name",  tableName);
            ttcp.put("column_name", columnName);
            Map<String, Object> ttcRow = sqlSession.selectOne(NS + "getCodeInfoInfo", ttcp);
            String codeInfo = ttcRow != null ? (String) ttcRow.get("code_info") : null;

            if (codeInfo != null) {
                Map<String, Object> cip = new HashMap<>();
                cip.put("code_info", codeInfo);
                cip.put("raw_values",    rawValues);
                cip.put("company_code",  companyCode);
                List<Map<String, Object>> ciRows = sqlSession.selectList(NS + "getCodeInfoList", cip);
                for (Map<String, Object> r : ciRows) {
                    String codeValue = (String) r.get("code_value");
                    if (!labelMap.containsKey(codeValue)) {
                        labelMap.put(codeValue, (String) r.get("code_name"));
                    }
                }
            }
        } catch (Exception e) {
            log.debug("code_info 조회 실패 (무시): {}", e.getMessage());
        }

        if (!labelMap.isEmpty()) {
            rows.forEach(row -> {
                String v = Objects.toString(row.get("value"), null);
                if (v != null && labelMap.containsKey(v)) {
                    row.put("label", labelMap.get(v));
                }
            });
        }

        return rows;
    }
}
