package incheon.com.config.annotation;

import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class NotSqlReservedValidator implements ConstraintValidator<NotSqlReserved, String> {

    private static final Set<String> SYS_COLS = Set.of("tableoid","xmin","xmax","cmin","cmax","ctid");
    private static final Map<String, Set<String>> CACHE = new ConcurrentHashMap<>();

    private final ResourceLoader resourceLoader;

    public NotSqlReservedValidator(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    private String wordsResource;
    private boolean caseInsensitive;
    private boolean blockSystemColumns;
    private Set<String> extraLower;

    @Override
    public void initialize(NotSqlReserved ann) {
        this.wordsResource = ann.wordsResource();
        this.caseInsensitive = ann.caseInsensitive();
        this.blockSystemColumns = ann.blockSystemColumns();
        this.extraLower = toLowerSet(Arrays.asList(ann.extra()));
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext ctx) {
        if (!StringUtils.hasText(value)) return true;
        String v = caseInsensitive ? value.trim().toLowerCase(Locale.ROOT) : value.trim();

        Set<String> deny = loadWords(wordsResource, caseInsensitive, blockSystemColumns, extraLower);
        if (deny.contains(v)) {
            ctx.disableDefaultConstraintViolation();
            ctx.buildConstraintViolationWithTemplate("예약어는 사용할 수 없습니다: " + value)
                    .addConstraintViolation();
            return false;
        }
        return true;
    }

    private Set<String> loadWords(String resourcePath, boolean toLower, boolean includeSysCols, Set<String> extra) {
        String key = resourcePath + "|" + toLower + "|" + includeSysCols + "|" + extra.hashCode();
        return CACHE.computeIfAbsent(key, k -> {
            Set<String> out = new HashSet<>();
            try {
                Resource res = resolve(resourcePath);
                try (BufferedReader br = new BufferedReader(
                        new InputStreamReader(res.getInputStream(), StandardCharsets.UTF_8))) {
                    String line;
                    while ((line = br.readLine()) != null) {
                        String s = line.trim();
                        if (s.isEmpty() || s.startsWith("#")) continue;
                        out.add(toLower ? s.toLowerCase(Locale.ROOT) : s);
                    }
                }
            } catch (Exception e) {
                throw new IllegalStateException("예약어 파일을 로드할 수 없습니다: " + resourcePath, e);
            }
            if (includeSysCols) out.addAll(SYS_COLS);
            out.addAll(extra);
            return Collections.unmodifiableSet(out);
        });
    }

    private Resource resolve(String resourcePath) {
        if (resourceLoader != null) return resourceLoader.getResource(resourcePath);
        return new PathMatchingResourcePatternResolver().getResource(resourcePath);
    }

    private Set<String> toLowerSet(Collection<String> items) {
        if (items == null) return Set.of();
        Set<String> s = new HashSet<>();
        for (String it : items) {
            if (it == null || it.isBlank()) continue;
            s.add(it.toLowerCase(Locale.ROOT));
        }
        return s;
    }
}
