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; }
}
}