package ai.decompile.ai.service.tools;

import ai.decompile.analysis.model.repository.StaticImportRepository;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Log4j2
@Component
@RequiredArgsConstructor
@Transactional(readOnly = true)
@ConditionalOnProperty(name = "app.ai.enabled", havingValue = "true", matchIfMissing = true)
@SuppressWarnings("unused")
public class ImportTools {
  private final StaticImportRepository importRepository;
  private final AnalysisResolver analysisResolver;

  private static final int MAX_IMPORTS = 30;

  @Tool(
      description =
          """
      Search for imported API functions by name or DLL. Use this when the user asks
      about specific API usage (e.g., "CreateFile", "socket", "RegSetValue", "InternetOpen")
      or wants to see what DLLs are imported.
      Returns up to 30 matching imports with their name, DLL, address and resolved name.
      """)
  public List<ImportSummary> searchImports(
      @ToolParam(
              description =
                  "API function name or DLL name to search for. Examples: 'CreateFile', 'kernel32', 'recv', 'WS2_32'")
          String name,
      @ToolParam(
              description =
                  "The binary ID being analyzed, as shown in the [BINARY OVERVIEW] section.")
          String binaryId) {

    log.debug("searchImports called: name='{}', binaryId={}", name, binaryId);

    if (Objects.isNull(name) || name.isBlank()) {
      return List.of();
    }

    var analysis = analysisResolver.resolve(binaryId);

    if (Objects.isNull(analysis)) {
      return List.of();
    }

    var results =
        importRepository.searchByNameOrDll(analysis.getId(), name).stream()
            .limit(MAX_IMPORTS)
            .map(
                i ->
                    new ImportSummary(
                        i.getName(),
                        i.getDll(),
                        i.getAddress(),
                        i.getOrdinal(),
                        i.getResolvedName()))
            .toList();

    log.debug("searchImports: name '{}' returned {} results", name, results.size());

    return results;
  }

  @Tool(
      description =
          """
      Get library/framework diagnostics for the binary. Identifies which
      libraries are linked, detected class names, and API category breakdown
      (e.g., Crypto, Network, GUI, File, Registry, Process).
      Use this when the user asks about frameworks, libraries, or technology stack.
      """)
  public String getLibraryDiagnostics(
      @ToolParam(
              description =
                  "The binary ID being analyzed, as shown in the [BINARY OVERVIEW] section.")
          String binaryId) {

    log.debug("getLibraryDiagnostics called: binaryId={}", binaryId);

    var analysis = analysisResolver.resolve(binaryId);

    if (Objects.isNull(analysis)) {
      return "No analysis found for binary " + binaryId;
    }

    var imports = importRepository.findByAnalysisIdOrderByDllAscNameAsc(analysis.getId());

    if (imports.isEmpty()) {
      return "No imports detected in this binary.";
    }

    var resolvedImports =
        imports.stream().filter(i -> Objects.nonNull(i.getResolvedName())).toList();

    var libraryNames = new LinkedHashSet<String>();
    var classNames = new LinkedHashSet<String>();
    var categoryCounts = new java.util.HashMap<String, Integer>();

    for (var imp : resolvedImports) {
      var sym = imp.getLibrarySymbol();

      if (Objects.nonNull(sym)) {
        libraryNames.add(sym.getLibrary().getName());

        if (Objects.nonNull(sym.getClassName())) {
          classNames.add(sym.getClassName());
        }

        var category = sym.getCategory();

        if (Objects.nonNull(category)) {
          categoryCounts.merge(category, 1, Integer::sum);
        }
      }
    }

    var sb = new StringBuilder();
    sb.append("[LIBRARY DIAGNOSTICS]\n\n");

    if (!libraryNames.isEmpty()) {
      sb.append("Detected libraries: ").append(String.join(", ", libraryNames)).append("\n\n");
    }

    if (!classNames.isEmpty()) {
      var topClasses = classNames.stream().sorted().limit(15).toList();
      sb.append("Detected classes (").append(classNames.size()).append("): ");
      sb.append(String.join(", ", topClasses)).append("\n\n");
    }

    if (!categoryCounts.isEmpty()) {
      sb.append("API category breakdown:\n");
      categoryCounts.entrySet().stream()
          .sorted((a, b) -> b.getValue().compareTo(a.getValue()))
          .limit(10)
          .forEach(
              e ->
                  sb.append("  ")
                      .append(e.getKey())
                      .append(": ")
                      .append(e.getValue())
                      .append("\n"));
    }

    sb.append("\nTotal imports: ")
        .append(imports.size())
        .append(" (")
        .append(resolvedImports.size())
        .append(" resolved)");

    log.debug(
        "getLibraryDiagnostics: {} imports, {} libraries, {} classes",
        imports.size(),
        libraryNames.size(),
        classNames.size());

    return sb.toString();
  }

  public record ImportSummary(
      String name, String dll, String address, int ordinal, String resolvedName) {}
}
