using System; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using FinlyticBackend.Util; using FinlyticCore.Dtos; using FinlyticCore.Models.Trades; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace FinlyticBackend.Controllers; [ApiController] [Authorize] [Route("api/v1/user/trades")] public class UserTradesController : ControllerBase { private readonly WebMqttClient _mqttClient; private readonly ILogger _logger; public UserTradesController(WebMqttClient mqttClient, ILogger logger) { _mqttClient = mqttClient; _logger = logger; } /// /// Liest die eindeutige UserId aus den Claims des authentifizierten Bearer Tokens. /// private string GetUserIdFromClaims() { var claimUserId = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub") ?? User.FindFirstValue("nameid"); if (!string.IsNullOrWhiteSpace(claimUserId)) { return claimUserId; } _logger.LogWarning("[UserTradesController] Claim NameIdentifier not found for authenticated request. Falling back to default_user."); return "default_user"; } /// /// Retrieves a list of trades for the current authenticated user (including global proposals). /// [HttpGet] public async Task GetUserTrades([FromQuery] string? isin = null, [FromQuery] string? status = null) { try { if (!_mqttClient.IsConnected) { return StatusCode(503, new { error = "MQTT Broker disconnected" }); } var userId = GetUserIdFromClaims(); // FIX: UserId explizit an GetTradesRequest übergeben! var request = new GetTradesRequest(isin, status, userId); var trades = await _mqttClient.SendRpcRequestAsync, GetTradesRequest>( "trades_Get", request, TimeSpan.FromSeconds(5)); return Ok(trades ?? new List()); } catch (Exception ex) { _logger.LogError(ex, "Failed to retrieve user trades via MQTT RPC."); return StatusCode(500, new { error = "Internal server error while fetching trades" }); } } /// /// Public endpoint to retrieve active global proposals for guest users. /// [HttpGet("/api/v1/trades/public")] [AllowAnonymous] public async Task GetPublicProposals([FromQuery] string? isin = null) { try { if (!_mqttClient.IsConnected) { return StatusCode(503, new { error = "MQTT Broker disconnected" }); } // Status = Proposed für anonyme Öffentliche Anfragen var request = new GetTradesRequest(isin, "Proposed", UserId: null); var proposals = await _mqttClient.SendRpcRequestAsync, GetTradesRequest>( "trades_Get", request, TimeSpan.FromSeconds(5)); return Ok(proposals ?? new List()); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to retrieve public trade proposals via MQTT RPC."); return Ok(new List()); } } /// /// Accepts a proposed trade and assigns it to the current user's portfolio. /// [HttpPost("accept")] public async Task AcceptTrade([FromBody] TradeAcceptanceDto request) { try { if (!_mqttClient.IsConnected) { return StatusCode(503, new { error = "MQTT Broker disconnected" }); } if (string.IsNullOrWhiteSpace(request.AnalysisId) && string.IsNullOrWhiteSpace(request.TradeId)) { return BadRequest(new { error = "AnalysisId or TradeId is required" }); } // FIX: UserId felsenfest aus den authentifizierten Claims überschreiben request.UserId = GetUserIdFromClaims(); var result = await _mqttClient.SendRpcRequestAsync( "trades_Accept", request, TimeSpan.FromSeconds(5)); if (result != null) { return Ok(result); } // Fallback Fire-and-Forget await _mqttClient.PublishAsync($"finlytic/trades/accept/{request.Isin}", request); return Ok(new { status = "Accepted", analysisId = request.AnalysisId, tradeId = request.TradeId, userId = request.UserId }); } catch (Exception ex) { _logger.LogError(ex, "Failed to accept trade proposal via MQTT RPC."); return StatusCode(500, new { error = "Internal server error" }); } } /// /// Closes an active trade. /// [HttpPost("{id}/close")] public async Task CloseTrade(string id, [FromBody] CloseTradeRequest? request) { try { if (!_mqttClient.IsConnected) { return StatusCode(503, new { error = "MQTT Broker disconnected" }); } var closeReq = request ?? new CloseTradeRequest { UserExitPrice = 100.0m, CloseReason = "UserManualClose" }; var result = await _mqttClient.SendRpcRequestAsync( $"trades_Close/{id}", closeReq, TimeSpan.FromSeconds(5)); if (result != null) { return Ok(result); } return Ok(new { status = "Closed", tradeId = id }); } catch (Exception ex) { _logger.LogError(ex, "Failed to close trade {Id} via MQTT RPC.", id); return StatusCode(500, new { error = "Internal server error while closing trade" }); } } /// /// Rejects a proposed trade. /// [HttpPost("{id}/reject")] public async Task RejectTrade(string id, [FromBody] CloseTradeRequest? request) { try { if (!_mqttClient.IsConnected) { return StatusCode(503, new { error = "MQTT Broker disconnected" }); } var closeReq = request ?? new CloseTradeRequest { CloseReason = "UserRejected" }; var result = await _mqttClient.SendRpcRequestAsync( $"trades_Reject/{id}", closeReq, TimeSpan.FromSeconds(5)); if (result != null) { return Ok(result); } return Ok(new { status = "Rejected", tradeId = id }); } catch (Exception ex) { _logger.LogError(ex, "Failed to reject trade {Id} via MQTT RPC.", id); return StatusCode(500, new { error = "Internal server error while rejecting trade" }); } } }