using Elecciones.Core.DTOs; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; using System.Threading.RateLimiting; using System.Threading.Tasks; namespace Elecciones.Infrastructure.Services; public class ElectoralApiService : IElectoralApiService { private readonly IHttpClientFactory _httpClientFactory; private readonly IConfiguration _configuration; private readonly ILogger _logger; private readonly RateLimiter _rateLimiter; public ElectoralApiService( IHttpClientFactory httpClientFactory, IConfiguration configuration, ILogger logger, RateLimiter rateLimiter) { _httpClientFactory = httpClientFactory; _configuration = configuration; _logger = logger; _rateLimiter = rateLimiter; } public async Task GetAuthTokenAsync() { using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); if (lease.IsAcquired) { var client = _httpClientFactory.CreateClient("ElectoralApiClient"); var username = _configuration["ElectoralApi:Username"]; var password = _configuration["ElectoralApi:Password"]; if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) return null; var request = new HttpRequestMessage(HttpMethod.Get, "/api/createtoken"); request.Headers.Add("username", username); request.Headers.Add("password", password); var response = await client.SendAsync(request); if (!response.IsSuccessStatusCode) return null; return await response.Content.ReadFromJsonAsync(); } return null; } public async Task GetCatalogoAmbitosAsync(string authToken, int categoriaId) { // "Pedir una ficha". Este método ahora devuelve un "lease" (permiso). // Si no hay fichas, esperará aquí automáticamente hasta que se rellene el cubo. /* using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); // Si se nos concede el permiso para proceder... if (lease.IsAcquired) {*/ var client = _httpClientFactory.CreateClient("ElectoralApiClient"); var request = new HttpRequestMessage(HttpMethod.Get, $"/api/catalogo/getCatalogo?categoriaId={categoriaId}"); request.Headers.Add("Authorization", $"Bearer {authToken}"); var response = await client.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; /* } // Si no se pudo obtener un permiso (ej. la cola está llena), devolvemos null. return null;*/ } public async Task?> GetAgrupacionesAsync(string authToken, string distritoId, int categoriaId) { // "Pedir una ficha". Este método ahora devuelve un "lease" (permiso). // Si no hay fichas, esperará aquí automáticamente hasta que se rellene el cubo. /* using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); // Si se nos concede el permiso para proceder... if (lease.IsAcquired) {*/ var client = _httpClientFactory.CreateClient("ElectoralApiClient"); var requestUri = $"/api/catalogo/getAgrupaciones?distritoId={distritoId}&categoriaId={categoriaId}"; var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Add("Authorization", $"Bearer {authToken}"); var response = await client.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync>() : null; /* } // Si no se pudo obtener un permiso (ej. la cola está llena), devolvemos null. return null;*/ } public async Task GetResultadosAsync(string authToken, string distritoId, string? seccionId, string? municipioId, int categoriaId) { using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); if (!lease.IsAcquired) return null; var client = _httpClientFactory.CreateClient("ElectoralApiClient"); var requestUri = $"/api/resultados/getResultados?distritoId={distritoId}&seccionId={seccionId}&categoriaId={categoriaId}"; if (!string.IsNullOrEmpty(municipioId)) { requestUri += $"&municipioId={municipioId}"; } var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Add("Authorization", $"Bearer {authToken}"); try { var response = await client.SendAsync(request); // --- APLICAMOS LA MISMA LÓGICA DEFENSIVA --- if (response.IsSuccessStatusCode) { try { // Leemos el contenido como un string primero para poder loguearlo si falla. var contentString = await response.Content.ReadAsStringAsync(); if (string.IsNullOrEmpty(contentString)) { _logger.LogWarning("La API devolvió 200 OK pero con cuerpo vacío para getResultados. URI: {uri}", requestUri); return null; } return JsonSerializer.Deserialize(contentString); } catch (JsonException ex) { _logger.LogWarning(ex, "La API devolvió una respuesta no-JSON para getResultados. URI: {uri}", requestUri); return null; } } _logger.LogWarning("La API devolvió un código de error {statusCode} para getResultados. URI: {uri}", response.StatusCode, requestUri); return null; } catch (Exception ex) { _logger.LogError(ex, "La petición HTTP a getResultados falló. URI: {uri}", requestUri); return null; } } public async Task GetBancasAsync(string authToken, string distritoId, string? seccionProvincialId, int categoriaId) { using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); if (!lease.IsAcquired) return null; var client = _httpClientFactory.CreateClient("ElectoralApiClient"); var requestUri = $"/api/resultados/getBancas?distritoId={distritoId}&categoriaId={categoriaId}"; if (!string.IsNullOrEmpty(seccionProvincialId)) { requestUri += $"&seccionProvincialId={seccionProvincialId}"; } var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Add("Authorization", $"Bearer {authToken}"); try { var response = await client.SendAsync(request); // --- CORRECCIÓN FINAL --- // Eliminamos la comprobación de ContentLength. Confiamos en el try-catch. if (response.IsSuccessStatusCode) { try { return await response.Content.ReadFromJsonAsync(); } catch (JsonException ex) { // Esto se activará si el cuerpo está vacío o no es un JSON válido. _logger.LogWarning(ex, "La API devolvió una respuesta no-JSON para getBancas. URI: {uri}", requestUri); return null; } } _logger.LogWarning("La API devolvió un código de error {statusCode} para getBancas. URI: {uri}", response.StatusCode, requestUri); return null; } catch (Exception ex) { _logger.LogError(ex, "La petición HTTP a getBancas falló. URI: {uri}", requestUri); return null; } } public async Task?> GetTelegramasTotalizadosAsync(string authToken, string distritoId, string seccionId, int? categoriaId = null) { // "Pedir una ficha". Este método ahora devuelve un "lease" (permiso). // Si no hay fichas, esperará aquí automáticamente hasta que se rellene el cubo. /* using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); // Si se nos concede el permiso para proceder... if (lease.IsAcquired) {*/ var client = _httpClientFactory.CreateClient("ElectoralApiClient"); var requestUri = $"/api/resultados/getTelegramasTotalizados?distritoId={distritoId}&seccionId={seccionId}"; if (categoriaId.HasValue) { requestUri += $"&categoriaId={categoriaId.Value}"; } var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Add("Authorization", $"Bearer {authToken}"); var response = await client.SendAsync(request); // Ahora deserializamos al tipo correcto: List return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync>() : null; /* } // Si no se pudo obtener un permiso (ej. la cola está llena), devolvemos null. return null;*/ } public async Task GetTelegramaFileAsync(string authToken, string mesaId) { using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); if (!lease.IsAcquired) return null; var client = _httpClientFactory.CreateClient("ElectoralApiClient"); var requestUri = $"/api/resultados/getFile?mesaId={mesaId}"; var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Add("Authorization", $"Bearer {authToken}"); try { var response = await client.SendAsync(request); // --- APLICAMOS LA MISMA CORRECCIÓN AQUÍ POR CONSISTENCIA --- if (response.IsSuccessStatusCode) { try { return await response.Content.ReadFromJsonAsync(); } catch (JsonException ex) { _logger.LogWarning(ex, "La API devolvió una respuesta no-JSON para getFile para la mesa {mesaId}", mesaId); return null; } } _logger.LogWarning("La API devolvió un código de error {statusCode} para la mesa {mesaId}", response.StatusCode, mesaId); return null; } catch (Exception ex) { _logger.LogError(ex, "La petición HTTP a getFile falló para la mesa {mesaId}", mesaId); return null; } } public async Task GetEstadoRecuentoGeneralAsync(string authToken, string distritoId, int categoriaId) { // "Pedir una ficha". Este método ahora devuelve un "lease" (permiso). // Si no hay fichas, esperará aquí automáticamente hasta que se rellene el cubo. /* using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); // Si se nos concede el permiso para proceder... if (lease.IsAcquired) {*/ var client = _httpClientFactory.CreateClient("ElectoralApiClient"); // La URL ahora usa el parámetro 'categoriaId' que se recibe var requestUri = $"/api/estados/estadoRecuento?distritoId={distritoId}&categoriaId={categoriaId}"; var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Add("Authorization", $"Bearer {authToken}"); var response = await client.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; /* } // Si no se pudo obtener un permiso (ej. la cola está llena), devolvemos null. return null;*/ } public async Task?> GetCategoriasAsync(string authToken) { // "Pedir una ficha". Este método ahora devuelve un "lease" (permiso). // Si no hay fichas, esperará aquí automáticamente hasta que se rellene el cubo. /* using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); // Si se nos concede el permiso para proceder... if (lease.IsAcquired) {*/ var client = _httpClientFactory.CreateClient("ElectoralApiClient"); var request = new HttpRequestMessage(HttpMethod.Get, "/api/catalogo/getCategorias"); request.Headers.Add("Authorization", $"Bearer {authToken}"); var response = await client.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync>() : null; /* } // Si no se pudo obtener un permiso (ej. la cola está llena), devolvemos null. return null;*/ } }