package com.codechecker.analyzer;

import com.codechecker.config.AppConfig;
import com.codechecker.model.ClassType;
import com.codechecker.model.FieldInfo;
import com.codechecker.parser.ClassFieldParser;
import com.codechecker.parser.TypeNameUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

/**
 * Dto/Vo 嵌套关系索引：反向查找祖先容器（用于影响分析与 API 跟进）。
 */
public class DtoNestIndex {
    private static final Set<String> LEAF_TYPES = Set.of(
            "String", "Integer", "int", "Long", "long", "Boolean", "boolean", "Double", "double",
            "Float", "float", "Short", "short", "Byte", "byte", "Character", "char",
            "BigDecimal", "BigInteger", "Date", "LocalDate", "LocalDateTime", "LocalTime",
            "Instant", "Timestamp", "Object", "Void", "void"
    );

    private final int maxDepth;
    private final Map<String, Set<String>> ancestorsOf = new LinkedHashMap<>();
    private final Map<String, String> sourceByClass = new HashMap<>();

    private DtoNestIndex(int maxDepth) {
        this.maxDepth = maxDepth;
    }

    public static DtoNestIndex build(Path repoRoot, AppConfig config) throws IOException {
        DtoNestIndex index = new DtoNestIndex(config.getNestMaxDepth());
        ClassFieldParser fieldParser = new ClassFieldParser();
        for (String dir : config.getModelDirs()) {
            Path root = repoRoot.resolve(dir.replace('\\', '/'));
            if (!Files.exists(root)) {
                continue;
            }
            try (Stream<Path> paths = Files.walk(root)) {
                paths.filter(path -> path.toString().endsWith(".java"))
                        .forEach(path -> {
                            String className = path.getFileName().toString().replace(".java", "");
                            if (ClassType.fromClassName(className) != ClassType.DTO
                                    && ClassType.fromClassName(className) != ClassType.VO) {
                                return;
                            }
                            try {
                                String source = Files.readString(path, StandardCharsets.UTF_8);
                                index.sourceByClass.put(className, source);
                            } catch (IOException ignored) {
                                // 跳过无法读取的文件
                            }
                        });
            }
        }
        for (Map.Entry<String, String> entry : index.sourceByClass.entrySet()) {
            String rootClass = entry.getKey();
            List<FieldInfo> fields = fieldParser.parseFields(entry.getValue(), rootClass);
            Set<String> visiting = new LinkedHashSet<>();
            index.walkNested(rootClass, fields, rootClass, 1, visiting, fieldParser);
        }
        return index;
    }

    /** 自身 + 所有祖先 Dto/Vo 类名（用于接口影响匹配） */
    public Set<String> expandImpactNames(String className) {
        Set<String> names = new LinkedHashSet<>();
        if (className != null && !className.isBlank()) {
            names.add(className);
            names.addAll(ancestorsOf.getOrDefault(className, Set.of()));
        }
        return names;
    }

    /** 嵌套类型的 @RequestBody 根 Dto 祖先（仅 Dto 后缀） */
    public Set<String> findRequestBodyRoots(String className) {
        Set<String> roots = new LinkedHashSet<>();
        if (className != null && className.endsWith("Dto")) {
            roots.add(className);
        }
        for (String ancestor : ancestorsOf.getOrDefault(className, Set.of())) {
            if (ancestor.endsWith("Dto")) {
                roots.add(ancestor);
            }
        }
        return roots;
    }

    public int getMaxDepth() {
        return maxDepth;
    }

    private void walkNested(String ownerClass, List<FieldInfo> fields, String rootAncestor,
                          int depth, Set<String> visiting, ClassFieldParser fieldParser) {
        if (depth > maxDepth) {
            return;
        }
        for (FieldInfo field : fields) {
            for (String nestedType : TypeNameUtils.peelDirectTypeNames(field.getType())) {
                if (isLeafType(nestedType) || nestedType.equals(ownerClass)) {
                    continue;
                }
                ancestorsOf.computeIfAbsent(nestedType, k -> new LinkedHashSet<>()).add(rootAncestor);
                if (!visiting.add(nestedType)) {
                    continue;
                }
                String nestedSource = sourceByClass.get(nestedType);
                if (nestedSource != null) {
                    List<FieldInfo> nestedFields = fieldParser.parseFields(nestedSource, nestedType);
                    walkNested(nestedType, nestedFields, rootAncestor, depth + 1, visiting, fieldParser);
                }
                visiting.remove(nestedType);
            }
        }
    }

    private boolean isLeafType(String simpleType) {
        return LEAF_TYPES.contains(simpleType) || simpleType.endsWith("[]");
    }
}
