using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using ERPCore.Dtos.Auth;
using ERPCore.System.Errors;

namespace ERPCore.Infra.Auth.AuthHex;

/// <summary>
/// HTTP implementation of <see cref="IAuthHexClient"/>. Registered as a typed
/// client (`AddHttpClient&lt;IAuthHexClient, AuthHexClient&gt;`) with its
/// `BaseAddress` bound from `AuthHex:BaseUrl`. Every call POSTs AuthHex's
/// `{ functionName, payload, reference }` envelope to the matching manager
/// route and unwraps the `{ statusCode, success, message, data }` response,
/// translating upstream failures into <see cref="DomainException"/>.
/// </summary>
public sealed class AuthHexClient : IAuthHexClient
{
    // WhenWritingNull: AuthHex's dispatcher reads payload fields as raw JsonElements and some
    // (e.g. RoleManager's isSystemRole) call type-specific getters like GetBoolean() that throw
    // on an explicit JSON null rather than treating it as "absent" — omit null properties instead
    // of serializing them, so unset nullable request fields behave as ContainsKey == false upstream.
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
    {
        PropertyNameCaseInsensitive = true,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    };

    private readonly HttpClient _http;

    public AuthHexClient(HttpClient http) => _http = http;

    // ---- UserManager --------------------------------------------------

    public Task<AuthHexSessionResult> RegisterAsync(RegisterRequest request, CancellationToken ct)
        => CallAsync<AuthHexSessionResult>("user", "registerUser", request, null, ct);

    public Task<AuthHexSessionResult> LoginAsync(LoginRequest request, CancellationToken ct)
        => CallAsync<AuthHexSessionResult>("user", "loginUser", request, null, ct);

    public Task<AuthHexSessionResult> VerifyOtpForLoginAsync(VerifyOtpForLoginRequest request, CancellationToken ct)
        => CallAsync<AuthHexSessionResult>("user", "VerifyOtpForLogin", request, null, ct);

    public Task<AuthHexSessionResult> RefreshTokenAsync(string refreshToken, string? deviceName, CancellationToken ct)
        => CallAsync<AuthHexSessionResult>("user", "refreshToken", new { refreshToken, deviceName }, null, ct);

    public Task<GetUserDetailsResponse> GetUserDetailsAsync(Guid userId, CancellationToken ct)
        => CallAsync<GetUserDetailsResponse>("user", "getUserDetails", new { userId }, null, ct);

    public Task<List<UserTypeDto>> ListUserTypesAsync(CancellationToken ct)
        => CallAsync<List<UserTypeDto>>("user", "listUserTypes", new { }, null, ct);

    public Task<List<SessionDto>> GetUserSessionsAsync(string bearerToken, CancellationToken ct)
        => CallAsync<List<SessionDto>>("user", "getUserSessions", new { }, bearerToken, ct);

    public Task ChangeUserStatusAsync(bool isActive, string bearerToken, CancellationToken ct)
        => CallVoidAsync("user", "ChangeUserStatus", new { isActive }, bearerToken, ct);

    public Task LockUserAccountAsync(bool isLocked, string bearerToken, CancellationToken ct)
        => CallVoidAsync("user", "LockUserAccount", new { isLocked }, bearerToken, ct);

    public Task ChangeUserPasswordAsync(ChangeUserPasswordRequest request, string bearerToken, CancellationToken ct)
        => CallVoidAsync("user", "ChangeUserPassword", request, bearerToken, ct);

    public Task<VerifyPasswordResponse> VerifyPasswordAsync(VerifyPasswordRequest request, string bearerToken, CancellationToken ct)
        => CallAsync<VerifyPasswordResponse>("user", "VerifyPassword", request, bearerToken, ct);

    public Task LogoutUserAsync(Guid userId, CancellationToken ct)
        => CallVoidAsync("user", "LogoutUser", new { userId }, null, ct);

    public Task<UserSummaryDto?> UpdateUserAsync(UpdateUserRequest request, string bearerToken, CancellationToken ct)
        => CallAsync<UserSummaryDto?>("user", "UpdateUser", request, bearerToken, ct);

    public Task<TwoFaSetupResponse> InitiateTwoFaSetupAsync(string bearerToken, CancellationToken ct)
        => CallAsync<TwoFaSetupResponse>("user", "initiateTwoFASetup", new { }, bearerToken, ct);

    public Task<CompleteTwoFaSetupResponse> CompleteTwoFaSetupAsync(CompleteTwoFaSetupRequest request, string bearerToken, CancellationToken ct)
        => CallAsync<CompleteTwoFaSetupResponse>("user", "completeTwoFASetup", request, bearerToken, ct);

    public Task VerifyTwoFaAsync(VerifyTwoFaRequest request, string bearerToken, CancellationToken ct)
        => CallVoidAsync("user", "verifyTwoFA", request, bearerToken, ct);

    public Task DisableTwoFaAsync(DisableTwoFaRequest request, string bearerToken, CancellationToken ct)
        => CallVoidAsync("user", "disableTwoFA", request, bearerToken, ct);

    public Task<TwoFaStatusResponse> GetTwoFaStatusAsync(string bearerToken, CancellationToken ct)
        => CallAsync<TwoFaStatusResponse>("user", "getTwoFAStatus", new { }, bearerToken, ct);

    // ---- RecoveryManager ------------------------------------------------

    public Task<ForgotPasswordResponse> ForgotPasswordAsync(ForgotPasswordRequest request, CancellationToken ct)
        => CallAsync<ForgotPasswordResponse>("recovery", "forgotPassword", request, null, ct);

    public Task<VerifyRecoveryOtpResponse> VerifyRecoveryOtpAsync(VerifyRecoveryOtpRequest request, CancellationToken ct)
        => CallAsync<VerifyRecoveryOtpResponse>("recovery", "verifyOTP", request, null, ct);

    public Task ResetPasswordAsync(ResetPasswordRequest request, CancellationToken ct)
        => CallVoidAsync("recovery", "resetPassword", request, null, ct);

    public Task ResetPasswordWithTokenAsync(ResetPasswordWithTokenRequest request, CancellationToken ct)
        => CallVoidAsync("recovery", "resetPasswordWithToken", request, null, ct);

    // ---- AltOptionManager -------------------------------------------------

    public Task<IsAvailableResponse> IsAvailableAsync(IsAvailableRequest request, CancellationToken ct)
        => CallAsync<IsAvailableResponse>("alt", "IsAvailable", request, null, ct);

    public Task<SendOtpResponse> SendOtpAsync(SendOtpRequest request, CancellationToken ct)
        => CallAsync<SendOtpResponse>("alt", "sendOtp", request, null, ct);

    public Task<AuthHexSessionResult> VerifyAltOtpAsync(VerifyAltOtpRequest request, CancellationToken ct)
        => CallAsync<AuthHexSessionResult>("alt", "VerifyOTP", request, null, ct);

    // ---- RoleManager --------------------------------------------------

    public Task<AuthHexRoleDto> CreateRoleAsync(CreateAuthHexRoleRequest request, CancellationToken ct)
        => CallAsync<AuthHexRoleDto>("role", "createRole", request, null, ct);

    public Task<List<AuthHexRoleDto>> ListRolesAsync(CancellationToken ct)
        => CallAsync<List<AuthHexRoleDto>>("role", "listRoles", new { }, null, ct);

    public Task<AuthHexRoleDto> GetRoleAsync(Guid roleId, CancellationToken ct)
        => CallAsync<AuthHexRoleDto>("role", "getRole", new { roleId }, null, ct);

    public Task<AuthHexRoleDto> UpdateRoleAsync(UpdateAuthHexRoleRequest request, CancellationToken ct)
        => CallAsync<AuthHexRoleDto>("role", "updateRole", request, null, ct);

    public Task DeleteRoleAsync(Guid roleId, CancellationToken ct)
        => CallVoidAsync("role", "deleteRole", new { roleId }, null, ct);

    // ---- Transport --------------------------------------------------------

    private async Task CallVoidAsync(string routeGroup, string functionName, object payload, string? bearerToken, CancellationToken ct)
        => await CallAsync<JsonElement?>(routeGroup, functionName, payload, bearerToken, ct);

    private async Task<T> CallAsync<T>(string routeGroup, string functionName, object payload, string? bearerToken, CancellationToken ct)
    {
        using var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"/api/{routeGroup}");
        if (!string.IsNullOrEmpty(bearerToken))
            httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);

        httpRequest.Content = JsonContent.Create(
            new AuthHexRequestBody { FunctionName = functionName, Payload = payload, Reference = string.Empty },
            options: JsonOptions);

        HttpResponseMessage httpResponse;
        try
        {
            httpResponse = await _http.SendAsync(httpRequest, ct);
        }
        catch (HttpRequestException)
        {
            throw new DomainException(ErrorCodes.AuthServiceUnavailable, "The authentication service is unreachable.", 503);
        }
        catch (TaskCanceledException) when (!ct.IsCancellationRequested)
        {
            throw new DomainException(ErrorCodes.AuthServiceUnavailable, "The authentication service timed out.", 503);
        }

        AuthHexEnvelope<T>? envelope;
        try
        {
            envelope = await httpResponse.Content.ReadFromJsonAsync<AuthHexEnvelope<T>>(JsonOptions, ct);
        }
        catch (JsonException)
        {
            throw new DomainException(ErrorCodes.AuthUpstreamError, "The authentication service returned an unreadable response.", 502);
        }

        if (envelope is null)
            throw new DomainException(ErrorCodes.AuthUpstreamError, "The authentication service returned an empty response.", 502);

        // Trust the outer HTTP status over envelope.Success — AuthHex has been observed
        // returning HTTP 500 with `success:true, data:null` on business failures (e.g.
        // invalid credentials), which would otherwise slip through as a "success" and
        // hand a null payload to the caller.
        if (!httpResponse.IsSuccessStatusCode || !envelope.Success)
        {
            var statusCode = !httpResponse.IsSuccessStatusCode
                ? (int)httpResponse.StatusCode
                : envelope.StatusCode is >= 400 and < 600 ? envelope.StatusCode : 400;
            throw new DomainException(ErrorCodes.AuthUpstreamError, envelope.Message ?? "Authentication request failed.", statusCode);
        }

        return envelope.Data!;
    }
}
