package ai.decompile.ai.service.tools;

import ai.decompile.ai.model.repository.EmbeddingChunkRepository;
import ai.decompile.ai.service.AiUtils;
import ai.decompile.ai.service.EmbeddingService;
import java.util.Objects;
import java.util.UUID;
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 BinaryTools {
  private final AnalysisResolver analysisResolver;
  private final EmbeddingService embeddingService;
  private final EmbeddingChunkRepository embeddingChunkRepository;

  private static final int SEMANTIC_TOP_K = 10;
  private static final double SEMANTIC_THRESHOLD = 0.5;

  @Tool(
      description =
          """
      Get complete binary metadata: filename, file size, format (PE/ELF/Mach-O),
      architecture (x86/x64/ARM), compiler, analysis engine, function count,
      entry point address, and analysis status.
      Use this for overview questions about the binary.
      """)
  public BinaryMetadataResult getBinaryMetadata(
      @ToolParam(
              description =
                  "The binary ID being analyzed, as shown in the [BINARY OVERVIEW] section.")
          String binaryId) {

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

    var analysis = analysisResolver.resolve(binaryId);

    if (Objects.isNull(analysis)) {
      return new BinaryMetadataResult(
          "unknown", 0, "unknown", "unknown", "unknown", "none", 0, "none", "no_analysis");
    }

    var binary = analysis.getBinary();

    return new BinaryMetadataResult(
        binary.getFilename(),
        binary.getFileSize(),
        Objects.nonNull(binary.getFormat()) ? binary.getFormat() : "unknown",
        Objects.nonNull(binary.getArchitecture()) ? binary.getArchitecture() : "unknown",
        Objects.nonNull(binary.getCompiler()) ? binary.getCompiler() : "unknown",
        analysis.getEngine(),
        analysis.getFunctionCount(),
        Objects.nonNull(analysis.getEntryPoint()) ? analysis.getEntryPoint() : "none",
        analysis.getStatus());
  }

  @Tool(
      description =
          """
      Search for functions and code sections semantically related to a query.
      Use this when the user asks about functionality (e.g., "network communication",
      "encryption routines", "file operations") rather than specific function names.
      Returns up to 10 most relevant code sections with their types and summaries.
      """)
  public String semanticSearch(
      @ToolParam(description = "Natural language query describing what to search for") String query,
      @ToolParam(
              description =
                  "The binary ID being analyzed, as shown in the [BINARY OVERVIEW] section.")
          String binaryId) {

    log.debug("semanticSearch called: query='{}', binaryId={}", query, binaryId);

    if (Objects.isNull(query) || query.isBlank()) {
      return "No query provided.";
    }

    UUID binaryUuid;

    try {
      binaryUuid = UUID.fromString(binaryId);
    } catch (IllegalArgumentException e) {
      return "Invalid binary ID format: " + binaryId;
    }

    try {
      var queryEmbedding = embeddingService.embed(query);
      var vectorStr = AiUtils.toVectorString(queryEmbedding);
      var chunks =
          embeddingChunkRepository.findSimilar(
              binaryUuid, vectorStr, SEMANTIC_THRESHOLD, SEMANTIC_TOP_K);

      if (chunks.isEmpty()) {
        return "No semantically relevant code sections found for query: " + query;
      }

      var sb = new StringBuilder();
      sb.append("[SEMANTIC SEARCH RESULTS for: ").append(query).append("]\n\n");

      int idx = 1;

      for (var chunk : chunks) {
        sb.append("Result ").append(idx++).append(" [").append(chunk.getChunkType()).append("]:\n");
        sb.append(AiUtils.truncate(chunk.getContent(), 1000)).append("\n\n");
      }

      log.debug("semanticSearch: query '{}' returned {} chunks", query, chunks.size());

      return sb.toString();
    } catch (Exception e) {
      log.warn("semanticSearch failed: {}", e.getMessage());
      return "Semantic search failed: " + e.getMessage();
    }
  }

  public record BinaryMetadataResult(
      String filename,
      long fileSize,
      String format,
      String architecture,
      String compiler,
      String engine,
      int functionCount,
      String entryPoint,
      String status) {}
}
