using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using RobotNet10.NavigationTune.Shared.Interfaces; using RobotNet10.NavigationTune.Shared.Models; namespace RobotNet10.NavigationTune.Controllers; /// /// REST API controller for tuning advisor - re-analyze tests and apply suggestions /// [ApiController] [Route("api/[controller]")] public class TuningAdvisorController( ITuningAdvisor tuningAdvisor, ITestRepository testRepository, IParameterManager parameterManager, ILogger logger) : ControllerBase { /// /// Re-analyze a completed test run using telemetry provided in the request body. /// Note: Telemetry data is not persisted to database, so the client must supply it. /// The primary analysis is done automatically in the orchestrator after test completion. /// [HttpPost("analyze/{testRunId}")] public async Task> AnalyzeTestRun( Guid testRunId, [FromBody] List? telemetry = null) { try { var testRun = await testRepository.GetByIdAsync(testRunId); if (testRun == null) return NotFound(new { error = $"Test run {testRunId} not found" }); if (testRun.Status != TestStatus.Completed) return BadRequest(new { error = "Only completed tests can be analyzed" }); var parameterSet = await parameterManager.GetByIdAsync(testRun.ParameterSetId); if (parameterSet == null) return NotFound(new { error = $"Parameter set {testRun.ParameterSetId} not found" }); if (telemetry == null || telemetry.Count < 10) return BadRequest(new { error = "Telemetry data is required for analysis (min 10 samples). Telemetry is not stored in the database; it is only available during test execution via SignalR." }); var metrics = testRun.Metrics; if (metrics == null) return BadRequest(new { error = "No metrics available for this test run" }); var report = tuningAdvisor.Analyze(telemetry, metrics, parameterSet); report.TestRunId = testRunId; return Ok(report); } catch (Exception ex) { logger.LogError(ex, "Error analyzing test run {TestRunId}", testRunId); return StatusCode(500, new { error = "Failed to analyze test run", details = ex.Message }); } } /// /// Apply selected suggestions to a parameter set (returns new set, does not persist). /// [HttpPost("apply")] public async Task> ApplySuggestions( [FromBody] ApplySuggestionsRequest request) { try { var parameterSet = await parameterManager.GetByIdAsync(request.ParameterSetId); if (parameterSet == null) return NotFound(new { error = $"Parameter set {request.ParameterSetId} not found" }); var selectedSuggestions = request.Report.Suggestions .Where(s => request.SuggestionIds.Contains(s.Id)) .ToList(); if (selectedSuggestions.Count == 0) return BadRequest(new { error = "No valid suggestions selected" }); var modified = tuningAdvisor.ApplyAllSuggestions(parameterSet, selectedSuggestions); return Ok(modified); } catch (Exception ex) { logger.LogError(ex, "Error applying suggestions"); return StatusCode(500, new { error = "Failed to apply suggestions", details = ex.Message }); } } /// /// Apply suggestions and save as a new parameter set. /// [HttpPost("apply-and-save")] public async Task> ApplyAndSave( [FromBody] ApplyAndSaveRequest request) { try { var parameterSet = await parameterManager.GetByIdAsync(request.ParameterSetId); if (parameterSet == null) return NotFound(new { error = $"Parameter set {request.ParameterSetId} not found" }); var selectedSuggestions = request.Report.Suggestions .Where(s => request.SuggestionIds.Contains(s.Id)) .ToList(); if (selectedSuggestions.Count == 0) return BadRequest(new { error = "No valid suggestions selected" }); var modified = tuningAdvisor.ApplyAllSuggestions(parameterSet, selectedSuggestions); // Create new parameter set with unique name modified.Id = Guid.NewGuid(); modified.Name = request.NewParameterSetName ?? $"{parameterSet.Name}_tuned_{DateTime.UtcNow:yyyyMMdd_HHmmss}"; modified.Description = $"Auto-tuned from '{parameterSet.Name}'. Applied {selectedSuggestions.Count} suggestion(s)."; modified.IsDefault = false; modified.CreatedAt = DateTime.UtcNow; modified.Version = parameterSet.Version + 1; await parameterManager.SaveAsync(modified); return Ok(modified); } catch (Exception ex) { logger.LogError(ex, "Error applying and saving suggestions"); return StatusCode(500, new { error = "Failed to apply and save suggestions", details = ex.Message }); } } } /// /// Request model for applying suggestions to a parameter set. /// public class ApplySuggestionsRequest { public Guid ParameterSetId { get; set; } public List SuggestionIds { get; set; } = new(); public TuningReport Report { get; set; } = null!; } /// /// Request model for applying suggestions and saving as a new parameter set. /// public class ApplyAndSaveRequest { public Guid ParameterSetId { get; set; } public List SuggestionIds { get; set; } = new(); public TuningReport Report { get; set; } = null!; public string? NewParameterSetName { get; set; } }