package com.huangzj.protoplugin.json;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.huangzj.protoplugin.mysql.JavaNames;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/** 将 JSON 对象推断为简单 Java Bean（含静态内部类与 {@link List} 字段）。 */
public final class JsonToJavaGenerator {

    private JsonToJavaGenerator() {}

    public static String generate(String json, String rootClassName, String packageName)
            throws JsonParseException {
        JsonElement root = JsonParser.parseString(json);
        if (!root.isJsonObject()) {
            throw new JsonParseException("根节点必须是 JSON 对象 {}");
        }
        NameAllocator names = new NameAllocator();
        String safeRoot = names.unique(safeClassName(rootClassName));
        ClassModel model = buildClassModel(root.getAsJsonObject(), safeRoot, names);
        return render(model, packageName);
    }

    private static String safeClassName(String raw) {
        if (raw == null || raw.isBlank()) {
            return "Generated";
        }
        String t = raw.trim();
        if (!Character.isJavaIdentifierStart(t.charAt(0))) {
            return "Generated";
        }
        for (int i = 1; i < t.length(); i++) {
            if (!Character.isJavaIdentifierPart(t.charAt(i))) {
                return "Generated";
            }
        }
        return t.substring(0, 1).toUpperCase(Locale.ROOT) + t.substring(1);
    }

    private static ClassModel buildClassModel(JsonObject obj, String className, NameAllocator names) {
        ClassModel m = new ClassModel(className);
        for (Map.Entry<String, JsonElement> e : obj.entrySet()) {
            String key = e.getKey();
            String field = JavaNames.toFieldName(key);
            TypeRef tr = inferType(e.getValue(), key, names);
            m.fields.add(new FieldModel(field, tr.typeName, tr.comment));
            m.innerClasses.addAll(tr.newInners);
        }
        return m;
    }

    private static TypeRef inferType(JsonElement el, String keyHint, NameAllocator names) {
        List<ClassModel> inners = new ArrayList<>();
        if (el == null || el.isJsonNull()) {
            return new TypeRef("Object", " // JSON null，类型需手动调整", inners);
        }
        if (el.isJsonPrimitive()) {
            return new TypeRef(primitiveJavaType(el.getAsJsonPrimitive()), "", inners);
        }
        if (el.isJsonArray()) {
            JsonArray arr = el.getAsJsonArray();
            if (arr.size() == 0) {
                return new TypeRef("java.util.List<Object>", "", inners);
            }
            JsonElement first = arr.get(0);
            TypeRef elem = inferType(first, keyHint, names);
            inners.addAll(elem.newInners);
            return new TypeRef("java.util.List<" + elem.typeName + ">", "", inners);
        }
        if (el.isJsonObject()) {
            String inner =
                    names.unique(JavaNames.toPascalCaseField(keyHint) + "Model");
            ClassModel nested = buildClassModel(el.getAsJsonObject(), inner, names);
            inners.add(nested);
            return new TypeRef(inner, "", inners);
        }
        return new TypeRef("Object", "", inners);
    }

    private static String primitiveJavaType(JsonPrimitive p) {
        if (p.isBoolean()) {
            return "boolean";
        }
        if (p.isString()) {
            return "String";
        }
        if (p.isNumber()) {
            try {
                BigDecimal bd = p.getAsBigDecimal();
                if (bd.scale() <= 0) {
                    try {
                        long v = bd.longValueExact();
                        if (v >= Integer.MIN_VALUE && v <= Integer.MAX_VALUE) {
                            return "int";
                        }
                        return "long";
                    } catch (ArithmeticException ex) {
                        return "java.math.BigInteger";
                    }
                }
            } catch (Exception ignored) {
            }
            return "double";
        }
        return "String";
    }

    private static String render(ClassModel root, String packageName) {
        StringBuilder sb = new StringBuilder();
        if (packageName != null && !packageName.isBlank()) {
            sb.append("package ").append(packageName.trim()).append(";\n\n");
        }
        if (needsListImport(root)) {
            sb.append("import java.util.List;\n\n");
        }
        emitClass(sb, root, 0);
        return sb.toString();
    }

    private static boolean needsListImport(ClassModel m) {
        for (FieldModel f : m.fields) {
            if (f.type.startsWith("java.util.List")) {
                return true;
            }
        }
        for (ClassModel inner : m.innerClasses) {
            if (needsListImport(inner)) {
                return true;
            }
        }
        return false;
    }

    private static void emitClass(StringBuilder sb, ClassModel m, int indent) {
        String pad = "    ".repeat(indent);
        sb.append(pad).append("public ");
        if (indent > 0) {
            sb.append("static ");
        }
        sb.append("class ").append(m.name).append(" {\n");
        for (FieldModel f : m.fields) {
            sb.append(pad)
                    .append("    private ")
                    .append(f.type)
                    .append(" ")
                    .append(f.name)
                    .append(";")
                    .append(f.comment)
                    .append("\n");
        }
        if (!m.fields.isEmpty()) {
            sb.append("\n");
        }
        for (FieldModel f : m.fields) {
            String cap = capitalize(f.name);
            sb.append(pad).append("    public ").append(f.type).append(" get").append(cap).append("() {\n");
            sb.append(pad).append("        return ").append(f.name).append(";\n");
            sb.append(pad).append("    }\n\n");
            sb.append(pad)
                    .append("    public void set")
                    .append(cap)
                    .append("(")
                    .append(f.type)
                    .append(" ")
                    .append(f.name)
                    .append(") {\n");
            sb.append(pad).append("        this.").append(f.name).append(" = ").append(f.name).append(";\n");
            sb.append(pad).append("    }\n\n");
        }
        TreeMap<String, ClassModel> innerByName = new TreeMap<>();
        for (ClassModel c : m.innerClasses) {
            innerByName.put(c.name, c);
        }
        for (ClassModel inner : innerByName.values()) {
            sb.append("\n");
            emitClass(sb, inner, indent + 1);
        }
        sb.append(pad).append("}\n");
    }

    private static String capitalize(String s) {
        if (s == null || s.isEmpty()) {
            return s;
        }
        return Character.toUpperCase(s.charAt(0)) + s.substring(1);
    }

    private static final class NameAllocator {
        private final Set<String> used = new HashSet<>();

        String unique(String base) {
            String b = base == null || base.isEmpty() ? "Nested" : base;
            int i = 0;
            String c = b;
            while (used.contains(c)) {
                c = b + (++i);
            }
            used.add(c);
            return c;
        }
    }

    private static final class TypeRef {
        final String typeName;
        final String comment;
        final List<ClassModel> newInners;

        TypeRef(String typeName, String comment, List<ClassModel> newInners) {
            this.typeName = typeName;
            this.comment = comment;
            this.newInners = newInners;
        }
    }

    private static final class FieldModel {
        final String name;
        final String type;
        final String comment;

        FieldModel(String name, String type, String comment) {
            this.name = name;
            this.type = type;
            this.comment = comment == null ? "" : comment;
        }
    }

    private static final class ClassModel {
        final String name;
        final List<FieldModel> fields = new ArrayList<>();
        final List<ClassModel> innerClasses = new ArrayList<>();

        ClassModel(String name) {
            this.name = name;
        }
    }
}
