package com.ga.api.service.upload;

import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.core.vo.upload.UploadTemplateColumnVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * LOOKUP 변환 규칙 최적화 캐시.
 *
 * preload()가 새 Map 인스턴스를 반환한다 — 싱글톤 상태를 보유하지 않으므로 thread-safe.
 * 호출자(UploadService)가 반환된 Map을 보유하고 TransformEngine에 전달한다.
 *
 * 캐시 구조: Map< "table:sourceField:targetField" → Map<sourceValue, targetValue> >
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LookupCache {

    private final LookupMapper lookupMapper;

    /** 허용 테이블 whitelist (SQL Injection 방지) */
    private static final Set<String> ALLOWED_TABLES = Set.of(
            "recruit_ledger", "maintain_ledger", "contract", "agent",
            "commission_rate", "payout_rule", "chargeback_rule",
            "exception_ledger", "payment",
            "common_code", "organization", "grade", "insurance_company", "product"
    );

    /** 컬럼 이름 허용 패턴 (영문/숫자/밑줄만) */
    private static final Pattern FIELD_PATTERN =
            Pattern.compile("^[A-Za-z][A-Za-z0-9_]{0,63}$");

    /**
     * LOOKUP 컬럼 목록에서 필요한 참조 데이터를 한 번에 로드하여 새 Map으로 반환.
     * 반환된 Map은 요청 범위에서만 사용되므로 동기화 불필요.
     */
    public Map<String, Map<String, Object>> preload(List<UploadTemplateColumnVO> columns) {
        Map<String, Map<String, Object>> cache = new HashMap<>();

        columns.stream()
                .filter(c -> "LOOKUP".equalsIgnoreCase(c.getTransformRule()))
                .forEach(c -> {
                    String key = buildKey(c.getLookupTable(), c.getLookupSourceField(), c.getLookupTargetField());
                    if (!cache.containsKey(key)) {
                        validateTableAndFields(c.getLookupTable(), c.getLookupSourceField(), c.getLookupTargetField());

                        List<Map<String, Object>> rows = lookupMapper.selectAll(
                                c.getLookupTable(), c.getLookupSourceField(), c.getLookupTargetField());

                        Map<String, Object> map = new HashMap<>(rows.size() * 2);
                        for (Map<String, Object> row : rows) {
                            Object src = row.get("source_value");
                            Object tgt = row.get("target_value");
                            if (src != null) {
                                map.put(src.toString(), tgt);
                            }
                        }
                        cache.put(key, map);
                        log.debug("LookupCache preloaded: {} → {} entries", key, map.size());
                    }
                });

        return cache;
    }

    /**
     * O(1) 조회. cache는 preload()의 반환값이어야 한다.
     */
    public Object lookup(Map<String, Map<String, Object>> cache,
                         String table, String sourceField, String value, String targetField) {
        if (value == null) return null;
        String key = buildKey(table, sourceField, targetField);
        Map<String, Object> map = cache.get(key);
        if (map == null) return null;
        return map.get(value);
    }

    private String buildKey(String table, String sourceField, String targetField) {
        return table + ":" + sourceField + ":" + targetField;
    }

    private void validateTableAndFields(String table, String sourceField, String targetField) {
        if (table == null || !ALLOWED_TABLES.contains(table)) {
            throw new BizException(ErrorCode.INVALID_PARAMETER,
                    "LOOKUP 허용되지 않는 테이블: " + table);
        }
        if (sourceField == null || !FIELD_PATTERN.matcher(sourceField).matches()) {
            throw new BizException(ErrorCode.INVALID_PARAMETER,
                    "LOOKUP 허용되지 않는 sourceField: " + sourceField);
        }
        if (targetField == null || !FIELD_PATTERN.matcher(targetField).matches()) {
            throw new BizException(ErrorCode.INVALID_PARAMETER,
                    "LOOKUP 허용되지 않는 targetField: " + targetField);
        }
    }
}
