package ai.decompile.ai.service.tools;

import ai.decompile.analysis.model.repository.StaticXrefRepository;
import java.util.List;
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 XrefTools {
  private final StaticXrefRepository xrefRepository;

  private static final int MAX_RESULTS = 20;

  @Tool(
      description =
          """
      Get all functions that CALL this function. Essential for understanding
      control flow — who invokes this function and under what conditions.
      Returns caller names, addresses, and call types (e.g., CALL, JUMP).
      """)
  public List<XrefResult> getCallers(
      @ToolParam(description = "The function ID (UUID from searchFunctions result)")
          String functionId) {

    log.debug("getCallers called: functionId={}", functionId);

    var funcId = UUID.fromString(functionId);
    var xrefs = xrefRepository.findByCalleeId(funcId);

    var results =
        xrefs.stream()
            .limit(MAX_RESULTS)
            .map(
                x ->
                    new XrefResult(
                        x.getCaller().getName(), x.getCaller().getAddress(), x.getType(), "caller"))
            .toList();

    log.debug("getCallers: functionId={} has {} callers", functionId, results.size());

    return results;
  }

  @Tool(
      description =
          """
      Get all functions that this function CALLS. Essential for understanding
      what APIs or subroutines a function depends on.
      Returns callee names, addresses, and call types (e.g., CALL, JUMP).
      """)
  public List<XrefResult> getCallees(
      @ToolParam(description = "The function ID (UUID from searchFunctions result)")
          String functionId) {

    log.debug("getCallees called: functionId={}", functionId);

    var funcId = UUID.fromString(functionId);
    var xrefs = xrefRepository.findByCallerId(funcId);

    var results =
        xrefs.stream()
            .limit(MAX_RESULTS)
            .map(
                x ->
                    new XrefResult(
                        x.getCallee().getName(), x.getCallee().getAddress(), x.getType(), "callee"))
            .toList();

    log.debug("getCallees: functionId={} has {} callees", functionId, results.size());

    return results;
  }

  @Tool(
      description =
          """
      Get ALL cross-references (both callers and callees) for a function.
      Use this for a complete picture of how a function fits in the call graph.
      Returns callers and callees with names, addresses, types and directions.
      """)
  public String getXrefGraph(
      @ToolParam(description = "The function ID (UUID from searchFunctions result)")
          String functionId) {

    log.debug("getXrefGraph called: functionId={}", functionId);

    var callers = xrefRepository.findByCalleeId(UUID.fromString(functionId));
    var callees = xrefRepository.findByCallerId(UUID.fromString(functionId));

    var sb = new StringBuilder();
    sb.append("[XREF GRAPH]\n\n");

    if (!callers.isEmpty()) {
      sb.append("CALLERS (").append(callers.size()).append("):\n");

      for (var x : callers) {
        sb.append("  ")
            .append(x.getCaller().getName())
            .append(" at ")
            .append(x.getCaller().getAddress())
            .append(" [")
            .append(x.getType())
            .append("]\n");
      }

      sb.append("\n");
    } else {
      sb.append("CALLERS: none (entry point or unreachable)\n\n");
    }

    if (!callees.isEmpty()) {
      sb.append("CALLEES (").append(callees.size()).append("):\n");

      for (var x : callees) {
        sb.append("  ")
            .append(x.getCallee().getName())
            .append(" at ")
            .append(x.getCallee().getAddress())
            .append(" [")
            .append(x.getType())
            .append("]\n");
      }
    } else {
      sb.append("CALLEES: none (leaf function)\n");
    }

    log.debug(
        "getXrefGraph: functionId={} has {} callers, {} callees",
        functionId,
        callers.size(),
        callees.size());

    return sb.toString();
  }

  public record XrefResult(String name, String address, String type, String direction) {}
}
