package com.graphtest;

import com.graphtest.model.*;

import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.regex.*;
import java.util.stream.Collectors;

public class GraphBuilder {

    private static final Pattern IMPORT_PATTERN =
        Pattern.compile("^\\s*import\\s+([\\w.]+);\\s*$", Pattern.MULTILINE);
    private static final Pattern STATIC_IMPORT_PATTERN =
        Pattern.compile("^\\s*import\\s+static\\s+([\\w.]+);\\s*$", Pattern.MULTILINE);
    private static final Pattern EXTENDS_PATTERN =
        Pattern.compile("(?:class|interface)\\s+\\w+(?:\\s*<[^>]*>)?\\s+extends\\s+([\\w,\\s<>]+?)(?:implements|\\{)", Pattern.MULTILINE);
    private static final Pattern IMPLEMENTS_PATTERN =
        Pattern.compile("class\\s+\\w+(?:\\s*<[^>]*>)?(?:\\s+extends\\s+[\\w<>]+)?\\s+implements\\s+([\\w,\\s<>]+?)\\s*\\{", Pattern.MULTILINE);
    private static final Pattern CLASS_DECL_PATTERN =
        Pattern.compile("(?:class|interface|enum)\\s+\\w+");

    public CodeGraph buildFromDirectory(Path directory) {
        CodeGraph graph = new CodeGraph();

        List<Path> javaFiles;
        try {
            javaFiles = Files.walk(directory)
                .filter(p -> p.toString().endsWith(".java"))
                .collect(Collectors.toList());
        } catch (IOException e) {
            graph.addBuildError("Cannot walk directory: " + e.getMessage());
            return graph;
        }

        for (Path file : javaFiles) {
            createNode(file, graph);
        }

        for (Path file : javaFiles) {
            String className = file.getFileName().toString().replace(".java", "");
            Node sourceNode = graph.getNode(className);
            if (sourceNode == null) continue;

            NodeStatus status = sourceNode.getStatus();
            if (status == NodeStatus.SYNTAX_ERROR
                || status == NodeStatus.EMPTY
                || status == NodeStatus.COMMENT_ONLY) {
                continue;
            }

            try {
                String content = Files.readString(file);
                parseImports(content, sourceNode, graph);
                parseStaticImports(content, sourceNode, graph);
                parseExtends(content, sourceNode, graph);
                parseImplements(content, sourceNode, graph);
            } catch (IOException e) {
                graph.addBuildError("Cannot read file: " + file);
            }
        }

        graph.detectCycles();
        return graph;
    }

    private void createNode(Path file, CodeGraph graph) {
        String className = file.getFileName().toString().replace(".java", "");
        try {
            String content = Files.readString(file);
            if (content.trim().isEmpty()) {
                graph.addNode(new Node(className, file.toString(), NodeStatus.EMPTY));
                return;
            }
            String stripped = stripComments(content).trim();
            if (stripped.isEmpty()) {
                graph.addNode(new Node(className, file.toString(), NodeStatus.COMMENT_ONLY));
                return;
            }
            if (hasSyntaxError(content)) {
                graph.addNode(new Node(className, file.toString(), NodeStatus.SYNTAX_ERROR));
                graph.addBuildError("Syntax error detected in file: " + file.getFileName());
                return;
            }
            graph.addNode(new Node(className, file.toString(), NodeStatus.OK));
        } catch (IOException e) {
            graph.addBuildError("Cannot read file: " + file);
        }
    }

    private void parseImports(String content, Node source, CodeGraph graph) {
        Matcher m = IMPORT_PATTERN.matcher(content);
        while (m.find()) {
            String fullImport = m.group(1);
            if (fullImport.contains("static")) continue;
            String simpleName = fullImport.substring(fullImport.lastIndexOf('.') + 1);
            addEdgeToGraph(source, simpleName, EdgeType.IMPORT, graph);
        }
    }

    private void parseStaticImports(String content, Node source, CodeGraph graph) {
        Matcher m = STATIC_IMPORT_PATTERN.matcher(content);
        while (m.find()) {
            String fullImport = m.group(1);
            String[] parts = fullImport.split("\\.");
            if (parts.length >= 2) {
                String className = parts[parts.length - 2];
                addEdgeToGraph(source, className, EdgeType.STATIC_IMPORT, graph);
            }
        }
    }

    private void parseExtends(String content, Node source, CodeGraph graph) {
        Matcher m = EXTENDS_PATTERN.matcher(content);
        while (m.find()) {
            String extendsClause = m.group(1).trim();
            for (String parent : extendsClause.split(",")) {
                String simpleName = parent.trim().replaceAll("<.*>", "").trim();
                if (!simpleName.isEmpty()) {
                    addEdgeToGraph(source, simpleName, EdgeType.EXTENDS, graph);
                }
            }
        }
    }

    private void parseImplements(String content, Node source, CodeGraph graph) {
        Matcher m = IMPLEMENTS_PATTERN.matcher(content);
        while (m.find()) {
            String implementsClause = m.group(1).trim();
            for (String iface : implementsClause.split(",")) {
                String simpleName = iface.trim().replaceAll("<.*>", "").trim();
                if (!simpleName.isEmpty()) {
                    addEdgeToGraph(source, simpleName, EdgeType.IMPLEMENTS, graph);
                }
            }
        }
    }

    private void addEdgeToGraph(Node source, String targetSimpleName, EdgeType type, CodeGraph graph) {
        Node target = graph.getNode(targetSimpleName);
        if (target == null) {
            target = new Node(targetSimpleName, "MISSING", NodeStatus.MISSING);
            graph.addNode(target);
        }
        graph.addEdge(new Edge(source, target, type));
    }

    private String stripComments(String content) {
        String result = content.replaceAll("//[^\n]*", "");
        result = result.replaceAll("(?s)/\\*.*?\\*/", "");
        return result;
    }

    private boolean hasSyntaxError(String content) {
        int braceCount = 0;
        boolean inString = false;
        boolean inLineComment = false;
        boolean inBlockComment = false;

        for (int i = 0; i < content.length(); i++) {
            char c = content.charAt(i);
            char next = i + 1 < content.length() ? content.charAt(i + 1) : '\0';

            if (inLineComment) { if (c == '\n') inLineComment = false; continue; }
            if (inBlockComment) { if (c == '*' && next == '/') { inBlockComment = false; i++; } continue; }
            if (!inString) {
                if (c == '/' && next == '/') { inLineComment = true; i++; continue; }
                if (c == '/' && next == '*') { inBlockComment = true; i++; continue; }
            }
            if (c == '"' && (i == 0 || content.charAt(i - 1) != '\\')) { inString = !inString; continue; }
            if (!inString) {
                if (c == '{') braceCount++;
                else if (c == '}') { braceCount--; if (braceCount < 0) return true; }
            }
        }

        if (braceCount != 0) return true;
        String stripped = stripComments(content).trim();
        return !stripped.isEmpty() && !CLASS_DECL_PATTERN.matcher(stripped).find();
    }
}
