using System; using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; using System.Threading.Tasks; using FinlyticBackend.Util; using FinlyticCore.Dtos; using FinlyticCore.Dtos.Fundamentals; using FinlyticCore.Dtos.Sentiment; using FinlyticCore.Dtos.TechnicalAnalysis; using FinlyticCore.Models.Analyzer; using FinlyticCore.Models.Trades; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace FinlyticBackend.Controllers; /// /// Request payload for triggering manual analysis (AOT-compliant). /// public record AnalyzeRequest( [property: JsonPropertyName("symbol")] string? Symbol, [property: JsonPropertyName("isin")] string? Isin, [property: JsonPropertyName("sector")] string? Sector, [property: JsonPropertyName("headline")] string? Headline, [property: JsonPropertyName("currentPrice")] decimal? CurrentPrice, [property: JsonPropertyName("riskScore")] int? RiskScore, [property: JsonPropertyName("minTimeframeValue")] int? MinTimeframeValue, [property: JsonPropertyName("maxTimeframeValue")] int? MaxTimeframeValue, [property: JsonPropertyName("timeframeUnit")] string? TimeframeUnit, [property: JsonPropertyName("instrumentType")] string? InstrumentType, [property: JsonPropertyName("userNotes")] string? UserNotes ); [ApiController] [Authorize] [Route("api/v1/analyze")] [EnableCors("AllowAll")] public class AnalyzeController : ControllerBase { private readonly WebMqttClient _mqttClient; private readonly ILogger _logger; public AnalyzeController(WebMqttClient mqttClient, ILogger logger) { _mqttClient = mqttClient; _logger = logger; } /// /// Triggers a manual analysis for an asset by gathering context in parallel and dispatching to FinlyticAnalyzer. /// [HttpPost("manual")] public async Task TriggerManualAnalysis([FromBody] AnalyzeRequest request) { try { if (string.IsNullOrWhiteSpace(request?.Isin) && string.IsNullOrWhiteSpace(request?.Symbol)) { return BadRequest(new { error = "ISIN or Symbol is required" }); } if (!_mqttClient.IsConnected) { return StatusCode(503, new { error = "Analysis service is currently unavailable." }); } string targetIsin = (request.Isin ?? request.Symbol ?? string.Empty).Trim().ToUpperInvariant(); string targetSymbol = (request.Symbol ?? request.Isin ?? string.Empty).Trim().ToUpperInvariant(); var isinReq = new IsinRequest(targetIsin); // 1. Parallelisiertes Context-Gathering (TA, Fundamentals, Sentiment) für minimale Latenz var taTask = FetchTaDataAsync(isinReq); var fundTask = FetchFundamentalsDataAsync(isinReq); var sentTask = FetchSentimentDataAsync(isinReq); await Task.WhenAll(taTask, fundTask, sentTask); var taData = await taTask; var fundData = await fundTask; var sentData = await sentTask; decimal lastCandlePrice = taData?.Candles != null && taData.Candles.Count > 0 ? (decimal)taData.Candles.Last().Close : 0m; decimal resolvedPrice = request.CurrentPrice ?? (lastCandlePrice > 0 ? lastCandlePrice : (fundData != null && fundData.CurrentPrice > 0 ? fundData.CurrentPrice : 100.0m)); var rpcRequest = new ManualAnalysisRpcRequest( Isin: targetIsin, Symbol: targetSymbol, Sector: !string.IsNullOrWhiteSpace(request.Sector) ? request.Sector : "Technology", Headline: !string.IsNullOrWhiteSpace(request.Headline) ? request.Headline : "Manual Analysis Triggered by User", CurrentPrice: resolvedPrice, RiskScore: request.RiskScore ?? 50, MinTimeframeValue: request.MinTimeframeValue ?? 4, MaxTimeframeValue: request.MaxTimeframeValue ?? 6, TimeframeUnit: !string.IsNullOrWhiteSpace(request.TimeframeUnit) ? request.TimeframeUnit : "Tage", InstrumentType: !string.IsNullOrWhiteSpace(request.InstrumentType) ? request.InstrumentType : "Stock", UserNotes: request.UserNotes ?? string.Empty, TaData: taData, FundamentalsData: fundData, SentimentData: sentData ); // 2. Ausführen des RPC Triggers am FinlyticAnalyzer (mit typisierter Response) try { var response = await _mqttClient.SendRpcRequestAsync( "analyzer_TriggerManual", rpcRequest, TimeSpan.FromSeconds(10)); if (response != null) { return Ok(response); } } catch (Exception ex) { _logger.LogWarning(ex, "[AnalyzeController] RPC analyzer_TriggerManual timed out or failed for ISIN '{Isin}'.", targetIsin); } return Ok(new { status = "AnalysisTriggered", isin = rpcRequest.Isin, message = "Manuelle KI-Analyse wurde gestartet, verarbeitet Ergebnisse im Hintergrund." }); } catch (Exception ex) { _logger.LogError(ex, "Failed to trigger manual analysis via MQTT RPC."); return StatusCode(500, new { error = "Internal server error triggering analysis." }); } } /// /// Fetches the currently active trade proposals from the FinlyticTrades service. /// [HttpGet("proposals")] public async Task GetActiveProposals() { try { if (!_mqttClient.IsConnected) { return StatusCode(503, new { error = "Analysis service is currently unavailable." }); } // AOT-sicherer RPC-Aufruf an FinlyticTrades für vorgeschlagene Trades var request = new GetTradesRequest(Isin: null, Status: "Proposed", UserId: null); var proposals = await _mqttClient.SendRpcRequestAsync, GetTradesRequest>( "trades_Get", request, TimeSpan.FromSeconds(4)); return Ok(proposals ?? new List()); } catch (Exception ex) { _logger.LogError(ex, "Failed to fetch active proposals via MQTT RPC."); return StatusCode(500, new { error = "Internal server error fetching proposals." }); } } private async Task FetchTaDataAsync(IsinRequest request) { try { return await _mqttClient.SendRpcRequestAsync( "ta_GetAnalysis", request, TimeSpan.FromSeconds(2)); } catch { return null; } } private async Task FetchFundamentalsDataAsync(IsinRequest request) { try { return await _mqttClient.SendRpcRequestAsync( "fundamentals_Get", request, TimeSpan.FromSeconds(2)); } catch { return null; } } private async Task FetchSentimentDataAsync(IsinRequest request) { try { return await _mqttClient.SendRpcRequestAsync( "sentiment_GetIsin", request, TimeSpan.FromSeconds(2)); } catch { return null; } } }