using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using GestionaDenuncias.Shared.Models;
using Microsoft.AspNetCore.Components.Authorization;

namespace GestionaDenunciasAN.Services;

public sealed class ApiDenunciasClient
{
    public const string HttpClientName = "ApiDenuncias";
    public const string AccessTokenClaim = "api_access_token";
    public const string TokenExpiresAtClaim = "api_token_expires_at";

    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);

    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly AuthenticationStateProvider _authenticationStateProvider;
    private readonly ILogger<ApiDenunciasClient> _logger;

    public ApiDenunciasClient(
        IHttpClientFactory httpClientFactory,
        IHttpContextAccessor httpContextAccessor,
        AuthenticationStateProvider authenticationStateProvider,
        ILogger<ApiDenunciasClient> logger)
    {
        _httpClientFactory = httpClientFactory;
        _httpContextAccessor = httpContextAccessor;
        _authenticationStateProvider = authenticationStateProvider;
        _logger = logger;
    }

    public Task<ApiLoginResponse> LoginAsync(LoginRequest request, CancellationToken cancellationToken = default)
        => SendAsync<ApiLoginResponse>(HttpMethod.Post, "api/auth/login", request, authorize: false, cancellationToken);

    public Task<ApiLoginPrepareResponse> PrepareLoginAsync(ApiLoginPrepareRequest request, CancellationToken cancellationToken = default)
        => SendAsync<ApiLoginPrepareResponse>(HttpMethod.Post, "api/auth/login/prepare", request, authorize: false, cancellationToken);

    public Task<ApiLoginResponse> CompleteLoginAsync(ApiLoginCompleteRequest request, CancellationToken cancellationToken = default)
        => SendAsync<ApiLoginResponse>(HttpMethod.Post, "api/auth/login/complete", request, authorize: false, cancellationToken);

    public Task LogoutAsync(CancellationToken cancellationToken = default)
        => SendAsync<object?>(HttpMethod.Post, "api/auth/logout", body: null, authorize: true, cancellationToken);

    public Task<ApiGlobalLeaksSessionDto?> GetGlobalLeaksSessionAsync(CancellationToken cancellationToken = default)
        => SendAsync<ApiGlobalLeaksSessionDto?>(HttpMethod.Get, "api/inbox/session", body: null, authorize: true, cancellationToken, allowNull: true);

    public Task<ApiGlobalLeaksSessionDto?> KeepGlobalLeaksSessionAliveAsync(CancellationToken cancellationToken = default)
        => SendAsync<ApiGlobalLeaksSessionDto?>(
            HttpMethod.Post,
            "api/inbox/session/keepalive",
            body: null,
            authorize: true,
            cancellationToken,
            allowNull: true);

    public Task<ApiLoginPrepareResponse> PrepareGlobalLeaksSessionRenewalAsync(CancellationToken cancellationToken = default)
        => SendAsync<ApiLoginPrepareResponse>(
            HttpMethod.Post,
            "api/inbox/session/renew/prepare",
            body: null,
            authorize: true,
            cancellationToken);

    public Task<ApiGlobalLeaksSessionDto> RenewGlobalLeaksSessionAsync(
        string authcode,
        string? pendingLoginId = null,
        CancellationToken cancellationToken = default)
        => SendAsync<ApiGlobalLeaksSessionDto>(
            HttpMethod.Post,
            "api/inbox/session/renew",
            new RenewGlobalLeaksSessionRequest(authcode, pendingLoginId),
            authorize: true,
            cancellationToken);

    public Task ClearGlobalLeaksSessionAsync(CancellationToken cancellationToken = default)
        => SendAsync<object?>(HttpMethod.Post, "api/inbox/session/clear", body: null, authorize: true, cancellationToken);

    public Task<InboxSnapshotResponse> LoadInboxAsync(CancellationToken cancellationToken = default)
        => SendAsync<InboxSnapshotResponse>(HttpMethod.Get, "api/inbox/reports", body: null, authorize: true, cancellationToken);

    public Task<ImportSummary> ImportReportAsync(
        ReportDto report,
        bool confirmDifferentOwner = false,
        CancellationToken cancellationToken = default)
        => SendAsync<ImportSummary>(
            HttpMethod.Post,
            $"api/inbox/reports/{Uri.EscapeDataString(report.Id)}/import",
            new ImportReportRequest(report, confirmDifferentOwner),
            authorize: true,
            cancellationToken);

    public Task<ReportDetailDto> GetReportDetailAsync(
        string reportId,
        string? lastAccess = null,
        CancellationToken cancellationToken = default)
    {
        var path = $"api/inbox/reports/{Uri.EscapeDataString(reportId)}/detail";
        if (!string.IsNullOrWhiteSpace(lastAccess))
        {
            path += $"?lastAccess={Uri.EscapeDataString(lastAccess)}";
        }

        return SendAsync<ReportDetailDto>(
            HttpMethod.Get,
            path,
            body: null,
            authorize: true,
            cancellationToken);
    }

    public Task<GestionaCreateFileResponse> CreateGestionaFileAsync(
        string subject,
        string documentSeries,
        string siaCode,
        CancellationToken cancellationToken = default)
        => PostAsync<GestionaCreateFileResponse>(
            "api/gestiona/files",
            new GestionaCreateFileRequest(subject, documentSeries, siaCode),
            cancellationToken);

    public Task OpenGestionaFileAsync(
        string fileUrl,
        string? fileOpenUrl,
        string assignedGroupCode,
        bool confidential,
        string freeTitle,
        CancellationToken cancellationToken = default)
        => PostAsync(
            "api/gestiona/files/open",
            new GestionaOpenFileRequest(fileUrl, fileOpenUrl, assignedGroupCode, confidential, freeTitle),
            cancellationToken);

    public Task AssignGestionaFileAsync(
        string fileUrl,
        string assignedGroupCode,
        CancellationToken cancellationToken = default)
        => PostAsync(
            "api/gestiona/files/assignees",
            new GestionaAssignFileRequest(fileUrl, assignedGroupCode),
            cancellationToken);

    public Task<GestionaExpedienteInfo?> GetGestionaExpedienteAsync(
        string fileUrl,
        CancellationToken cancellationToken = default)
        => GetAsync<GestionaExpedienteInfo?>(
            $"api/gestiona/files/by-url?fileUrl={Uri.EscapeDataString(fileUrl)}",
            cancellationToken,
            allowNull: true);

    public Task<GestionaAuditInfo?> GetGestionaFileLatestAuditAsync(
        string fileUrl,
        CancellationToken cancellationToken = default)
        => GetAsync<GestionaAuditInfo?>(
            $"api/gestiona/files/audit/latest?fileUrl={Uri.EscapeDataString(fileUrl)}",
            cancellationToken,
            allowNull: true);

    public Task<List<GestionaUploadHistoryEntry>> GetGestionaUploadHistoryAsync(CancellationToken cancellationToken = default)
        => GetAsync<List<GestionaUploadHistoryEntry>>("api/denuncias/gestiona-history", cancellationToken);

    public Task RegisterGestionaUploadHistoryAsync(
        GestionaUploadHistoryCreateRequest request,
        CancellationToken cancellationToken = default)
        => PostAsync("api/denuncias/gestiona-history", request, cancellationToken);

    public Task<GestionaEnsureThirdResponse> EnsureGestionaThirdAndLinkAsync(
        string fileUrl,
        ThirdPartyIdentityData thirdParty,
        CancellationToken cancellationToken = default)
        => PostAsync<GestionaEnsureThirdResponse>(
            "api/gestiona/thirds/ensure-link",
            new GestionaEnsureThirdRequest(fileUrl, thirdParty),
            cancellationToken);

    public Task<GestionaCreateFolderResponse> CreateGestionaFolderAsync(
        string fileUrl,
        string folderName,
        CancellationToken cancellationToken = default)
        => PostAsync<GestionaCreateFolderResponse>(
            "api/gestiona/folders",
            new GestionaCreateFolderRequest(fileUrl, folderName),
            cancellationToken);

    public async Task<string> UploadGestionaDocumentAsync(
        string fileUrl,
        byte[] contentBytes,
        string fileName,
        CancellationToken cancellationToken = default)
    {
        var response = await PostAsync<GestionaUploadDocumentResponse>(
            "api/gestiona/documents",
            new GestionaUploadDocumentRequest(fileUrl, contentBytes, fileName),
            cancellationToken);

        return response.DocumentUrl;
    }

    public Task TramitarGestionaDocumentAsync(
        string documentUrl,
        string assignedGroupCode,
        int? complaintId,
        bool isUpdate = false,
        string? updateSource = null,
        CancellationToken cancellationToken = default)
        => PostAsync(
            "api/gestiona/documents/tramitar",
            new GestionaTramitarDocumentoRequest(
                documentUrl,
                assignedGroupCode,
                complaintId,
                isUpdate,
                updateSource),
            cancellationToken);

    public Task<List<ExpedienteTerceroDto>> GetGestionaExpedientesPorTerceroAsync(
        string nif,
        DateTimeOffset? desde = null,
        DateTimeOffset? hasta = null,
        int maxPages = 1,
        int maxResults = 30,
        int maxParallel = 6,
        CancellationToken cancellationToken = default)
    {
        var query = new List<string>
        {
            $"maxPages={maxPages}",
            $"maxResults={maxResults}",
            $"maxParallel={maxParallel}"
        };

        if (desde.HasValue)
        {
            query.Add($"desde={Uri.EscapeDataString(desde.Value.ToString("O"))}");
        }

        if (hasta.HasValue)
        {
            query.Add($"hasta={Uri.EscapeDataString(hasta.Value.ToString("O"))}");
        }

        var path = $"api/gestiona/thirds/{Uri.EscapeDataString(nif)}/files?{string.Join('&', query)}";
        return GetAsync<List<ExpedienteTerceroDto>>(path, cancellationToken);
    }

    public Task<ManualPurgeResponse> ExecuteManualPurgeAsync(
        string date,
        CancellationToken cancellationToken = default)
        => PostAsync<ManualPurgeResponse>(
            "api/purge/manual",
            new ManualPurgeRequest(date),
            cancellationToken);

    public Task<ManualPurgeResponse> ExecuteCurrentManualPurgeAsync(
        CancellationToken cancellationToken = default)
        => PostAsync<ManualPurgeResponse>(
            "api/purge/manual/current",
            body: null,
            cancellationToken);

    public Task<AppConfigurationDto> GetAppConfigurationAsync(CancellationToken cancellationToken = default)
        => GetAsync<AppConfigurationDto>("api/configuration", cancellationToken);

    public Task<AppConfigurationDto> UpdateExternalUpdateCutoffDateAsync(
        string? date,
        CancellationToken cancellationToken = default)
        => SendAsync<AppConfigurationDto>(
            HttpMethod.Put,
            "api/configuration/external-update-cutoff",
            new UpdateExternalUpdateCutoffRequest(date),
            authorize: true,
            cancellationToken);

    public Task<WorkGroupAdministrationDto> GetWorkGroupAdministrationAsync(
        CancellationToken cancellationToken = default)
        => GetAsync<WorkGroupAdministrationDto>(
            "api/configuration/work-groups",
            cancellationToken);

    public Task<CurrentUserWorkGroupsDto> GetCurrentUserWorkGroupsAsync(
        CancellationToken cancellationToken = default)
        => GetAsync<CurrentUserWorkGroupsDto>(
            "api/configuration/work-groups/current",
            cancellationToken);

    public Task<WorkGroupAdministrationDto> UpdateUserWorkGroupsAsync(
        string username,
        IReadOnlyList<string> groupCodes,
        CancellationToken cancellationToken = default)
        => SendAsync<WorkGroupAdministrationDto>(
            HttpMethod.Put,
            $"api/configuration/work-groups/users/{Uri.EscapeDataString(username)}",
            new UpdateUserWorkGroupsRequest(groupCodes),
            authorize: true,
            cancellationToken);

    internal Task<T> GetAsync<T>(string path, CancellationToken cancellationToken = default, bool allowNull = false)
        => SendAsync<T>(HttpMethod.Get, path, body: null, authorize: true, cancellationToken, allowNull);

    internal Task<T> PostAsync<T>(string path, object? body, CancellationToken cancellationToken = default)
        => SendAsync<T>(HttpMethod.Post, path, body, authorize: true, cancellationToken);

    internal Task PostAsync(string path, object? body, CancellationToken cancellationToken = default)
        => SendAsync<object?>(HttpMethod.Post, path, body, authorize: true, cancellationToken);

    private async Task<T> SendAsync<T>(
        HttpMethod method,
        string path,
        object? body,
        bool authorize,
        CancellationToken cancellationToken,
        bool allowNull = false)
    {
        var client = _httpClientFactory.CreateClient(HttpClientName);
        using var request = new HttpRequestMessage(method, path);

        if (body is not null)
        {
            request.Content = JsonContent.Create(body, options: JsonOptions);
        }

        if (authorize)
        {
            var token = await GetAccessTokenAsync();
            if (string.IsNullOrWhiteSpace(token))
            {
                _logger.LogWarning("No hay token de API disponible para llamar a {Path}. Usuario autenticado={IsAuthenticated}",
                    path,
                    _httpContextAccessor.HttpContext?.User.Identity?.IsAuthenticated);
                throw new UnauthorizedAccessException("No hay token de API activo. Vuelve a iniciar sesion.");
            }

            _logger.LogInformation("Llamando a API protegida {Path}. Token presente={TokenPresent}", path, true);
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        }

        using var response = await client.SendAsync(request, cancellationToken);
        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            var message = await ReadErrorMessageAsync(response, cancellationToken);
            if (!string.IsNullOrWhiteSpace(message) &&
                !message.StartsWith("La API de denuncias ha respondido con", StringComparison.OrdinalIgnoreCase))
            {
                throw new UnauthorizedAccessException(message);
            }

            throw new UnauthorizedAccessException(authorize
                ? "La sesion de la aplicacion ha caducado. Vuelve a iniciar sesion."
                : "No se ha podido autorizar la peticion. Vuelve a iniciar sesion.");
        }

        if (!response.IsSuccessStatusCode)
        {
            var message = await ReadErrorMessageAsync(response, cancellationToken);
            throw new InvalidOperationException(message);
        }

        if (typeof(T) == typeof(object))
        {
            return default!;
        }

        var result = await response.Content.ReadFromJsonAsync<T>(JsonOptions, cancellationToken);
        if (result is null && !allowNull)
        {
            throw new InvalidOperationException("La API de denuncias no ha devuelto contenido valido.");
        }

        return result!;
    }

    private async Task<string?> GetAccessTokenAsync()
    {
        var token = _httpContextAccessor.HttpContext?.User.FindFirst(AccessTokenClaim)?.Value;
        if (!string.IsNullOrWhiteSpace(token))
        {
            return token;
        }

        try
        {
            var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
            return authState.User.FindFirst(AccessTokenClaim)?.Value;
        }
        catch
        {
            return null;
        }
    }

    private static async Task<string> ReadErrorMessageAsync(HttpResponseMessage response, CancellationToken cancellationToken)
    {
        try
        {
            var apiError = await response.Content.ReadFromJsonAsync<ApiError>(JsonOptions, cancellationToken);
            if (!string.IsNullOrWhiteSpace(apiError?.Error))
            {
                return apiError.Error;
            }
        }
        catch
        {
            // Fallback to raw content below.
        }

        var raw = await response.Content.ReadAsStringAsync(cancellationToken);
        return string.IsNullOrWhiteSpace(raw)
            ? $"La API de denuncias ha respondido con {(int)response.StatusCode}."
            : raw;
    }
}
