package ai.decompile.workspace.controller;

import ai.decompile.workspace.model.dto.BinaryDependencyResponse;
import ai.decompile.workspace.model.dto.BinaryResponse;
import ai.decompile.workspace.service.BinaryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequiredArgsConstructor
public class BinaryController {
  private final BinaryService binaryService;

  @Operation(
      summary = "List binaries of a project",
      description =
          "Retrieve all binaries belonging to a project, optionally filtered by filename.",
      tags = {"Binary"},
      operationId = "getBinaries")
  @GetMapping("/projects/{projectId}/binaries")
  public List<BinaryResponse> getBinaries(
      @PathVariable UUID projectId,
      @Parameter(
              description = "Filter binaries whose filename contains this value (case-insensitive)")
          @RequestParam(required = false)
          String name) {
    return binaryService.getBinaries(projectId, name);
  }

  @Operation(
      summary = "Upload a binary",
      description =
          "Upload a binary file to a project. Optionally specify an engine for static analysis.",
      tags = {"Binary"},
      operationId = "uploadBinary")
  @PostMapping(
      value = "/projects/{projectId}/binaries",
      consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
  @ResponseStatus(HttpStatus.CREATED)
  public BinaryResponse uploadBinary(
      @PathVariable UUID projectId,
      @RequestPart MultipartFile file,
      @Parameter(description = "Static analysis engine to use (IDA5, GHIDRA)")
          @RequestParam(required = false)
          String engine,
      @Parameter(description = "Companion DLL files to aid static analysis")
          @RequestPart(required = false)
          List<MultipartFile> dependencies) {
    return binaryService.uploadBinary(projectId, file, engine, dependencies);
  }

  @Operation(
      summary = "Upload companion DLLs for a binary",
      description =
          "Upload DLL files that the binary depends on, used during static analysis for better function identification.",
      tags = {"Binary"},
      operationId = "uploadDependencies")
  @PostMapping(
      value = "/binaries/{binaryId}/dependencies",
      consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
  public List<BinaryDependencyResponse> uploadDependencies(
      @PathVariable UUID binaryId,
      @Parameter(description = "DLL files to upload") @RequestPart List<MultipartFile> files) {
    return binaryService.uploadDependencies(binaryId, files);
  }

  @Operation(
      summary = "List companion DLLs for a binary",
      description = "Retrieve all dependency DLLs associated with a binary.",
      tags = {"Binary"},
      operationId = "getDependencies")
  @GetMapping("/binaries/{binaryId}/dependencies")
  public List<BinaryDependencyResponse> getDependencies(@PathVariable UUID binaryId) {
    return binaryService.getDependencies(binaryId);
  }

  @Operation(
      summary = "Delete a companion DLL",
      description = "Remove a specific dependency DLL from a binary.",
      tags = {"Binary"},
      operationId = "deleteDependency")
  @DeleteMapping("/binaries/{binaryId}/dependencies/{dependencyId}")
  @ResponseStatus(HttpStatus.NO_CONTENT)
  public void deleteDependency(@PathVariable UUID binaryId, @PathVariable UUID dependencyId) {
    binaryService.deleteDependency(binaryId, dependencyId);
  }

  @Operation(
      summary = "Trigger static analysis on a binary",
      description =
          "Trigger or re-trigger static analysis on an existing binary with the specified engine.",
      tags = {"Binary"},
      operationId = "analyzeBinary")
  @PostMapping("/binaries/{binaryId}/analyze")
  public ResponseEntity<Map<String, String>> analyzeBinary(
      @PathVariable UUID binaryId,
      @Parameter(description = "Static analysis engine to use (IDA5, GHIDRA)") @RequestParam
          String engine) {
    binaryService.requestStaticAnalysis(binaryId, engine);
    return ResponseEntity.accepted()
        .body(Map.of("message", "Static analysis requested with engine " + engine));
  }

  @Operation(
      summary = "Get binary details",
      description = "Retrieve details of a binary by its ID.",
      tags = {"Binary"},
      operationId = "getBinary")
  @GetMapping("/binaries/{binaryId}")
  public BinaryResponse getBinary(@PathVariable UUID binaryId) {
    return binaryService.getBinaryById(binaryId);
  }

  @Operation(
      summary = "Delete a binary",
      description = "Delete a binary and its file.",
      tags = {"Binary"},
      operationId = "deleteBinary")
  @DeleteMapping("/binaries/{binaryId}")
  @ResponseStatus(HttpStatus.NO_CONTENT)
  public void deleteBinary(@PathVariable UUID binaryId) {
    binaryService.deleteBinary(binaryId);
  }

  @Operation(
      summary = "Download a binary",
      description = "Download the binary file.",
      tags = {"Binary"},
      operationId = "downloadBinary")
  @GetMapping("/binaries/{binaryId}/download")
  public ResponseEntity<InputStreamResource> downloadBinary(@PathVariable UUID binaryId) {
    var resource = binaryService.downloadBinary(binaryId);
    return ResponseEntity.ok()
        .header(
            HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.filename() + "\"")
        .contentLength(resource.contentLength())
        .contentType(MediaType.APPLICATION_OCTET_STREAM)
        .body(new InputStreamResource(resource.inputStream()));
  }
}
