package ai.decompile.job.service.handler;

import ai.decompile.analysis.event.StaticAnalysisCompletedEvent;
import ai.decompile.analysis.service.AnalysisService;
import ai.decompile.analysis.service.SymbolResolutionService;
import ai.decompile.engine.service.EngineService;
import ai.decompile.job.model.entity.Job;
import ai.decompile.job.model.enums.JobStatus;
import ai.decompile.job.model.enums.JobType;
import ai.decompile.workspace.service.BinaryService;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

@Log4j2
@Component
@ConditionalOnProperty(name = "app.docker.enabled", havingValue = "true")
public class StaticAnalysisHandler implements JobHandler {
  private final Map<String, EngineService> engineServices;
  private final AnalysisService analysisService;
  private final SymbolResolutionService symbolResolutionService;
  private final BinaryService binaryService;
  private final ApplicationEventPublisher eventPublisher;
  private final ObjectMapper objectMapper;

  public StaticAnalysisHandler(
      List<EngineService> engineServiceList,
      AnalysisService analysisService,
      SymbolResolutionService symbolResolutionService,
      BinaryService binaryService,
      ApplicationEventPublisher eventPublisher,
      ObjectMapper objectMapper) {
    this.engineServices =
        engineServiceList.stream()
            .collect(Collectors.toMap(e -> e.supportedEngine().name(), Function.identity()));
    this.analysisService = analysisService;
    this.symbolResolutionService = symbolResolutionService;
    this.binaryService = binaryService;
    this.eventPublisher = eventPublisher;
    this.objectMapper = objectMapper;
  }

  @Override
  public void process(Job job) {
    var engineName = readEngineFromPayload(job.getPayload());
    log.info(
        "Processing STATIC_ANALYSIS job {} for binary {} in project {} with engine {}",
        job.getId(),
        job.getBinaryId(),
        job.getProjectId(),
        engineName);

    var engineService = engineServices.get(engineName);
    if (engineService == null) {
      job.setStatus(JobStatus.FAILED);
      job.setErrorMessage(
          "Unknown engine '" + engineName + "'. Available: " + engineServices.keySet());
      return;
    }

    var filePath = binaryService.getBinaryFilePath(job.getBinaryId());
    var dependencyPaths = binaryService.getDependencyFilePaths(job.getBinaryId());
    var result = engineService.analyze(filePath, dependencyPaths);

    var binary = binaryService.getBinary(job.getBinaryId());
    var analysisResponse = analysisService.saveResult(binary, job.getId(), engineName, result);

    symbolResolutionService.resolveImports(analysisResponse.id());

    binaryService.markStaticAnalysisDone(job.getBinaryId());

    job.setResult(toJson(Map.of("analysisId", analysisResponse.id().toString())));
    job.setStatus(JobStatus.COMPLETED);

    eventPublisher.publishEvent(
        new StaticAnalysisCompletedEvent(
            job.getBinaryId(),
            job.getProjectId(),
            binary.getProject().getWorkspace().getId(),
            analysisResponse.id(),
            engineName));

    log.info(
        "Job {} completed. engine={}, functionCount={}",
        job.getId(),
        engineName,
        analysisResponse.functionCount());
  }

  @Override
  public JobType supportedType() {
    return JobType.STATIC_ANALYSIS;
  }

  private String readEngineFromPayload(String payload) {
    try {
      var node = objectMapper.readTree(payload);
      return node.get("engine").asText();
    } catch (Exception e) {
      throw new RuntimeException("Failed to read engine from job payload", e);
    }
  }

  private String toJson(Object obj) {
    try {
      return objectMapper.writeValueAsString(obj);
    } catch (Exception e) {
      log.warn("Failed to serialize result to JSON", e);
      return null;
    }
  }
}
