using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using Tiku.Api.Contracts;
using Tiku.Domain.Tenancy;

namespace Tiku.IntegrationTests.Api;

internal sealed record TestAuthenticationTokens(string AccessToken, string RefreshToken);

internal static class AuthenticationTestClientExtensions
{
    public static async Task<TestAuthenticationTokens> LoginAsTenantAsync(
        this HttpClient client,
        Guid tenantId,
        string identifier,
        string password = PasswordTestUserExtensions.TestPassword)
    {
        SetTenantHeader(client, tenantId);
        var response = await client.PostAsJsonAsync(
            "/api/tenant/auth/login/password",
            new PasswordLoginDto
            {
                Realm = AuthRealm.Tenant,
                TenantCode = tenantId.ToString("N"),
                Identifier = identifier,
                Password = password
            });

        return await client.CompleteTenantAuthenticationAsync(response, tenantId, identifier);
    }

    public static async Task<TestAuthenticationTokens> LoginAsPlatformAsync(
        this HttpClient client,
        string identifier,
        string password = PasswordTestUserExtensions.TestPassword)
    {
        client.DefaultRequestHeaders.Remove("x-tenant-code");
        var response = await client.PostAsJsonAsync(
            "/api/tenant/auth/login/password",
            new PasswordLoginDto
            {
                Realm = AuthRealm.Platform,
                Identifier = identifier,
                Password = password
            });

        return await client.CompletePlatformAuthenticationAsync(response, identifier);
    }

    public static async Task<TestAuthenticationTokens> CompleteTenantAuthenticationAsync(
        this HttpClient client,
        HttpResponseMessage response,
        Guid tenantId,
        string _)
    {
        SetTenantHeader(client, tenantId);
        using var authentication = await ReadSuccessfulJsonAsync(response);
        var root = authentication.RootElement;
        var status = root.GetProperty("status").GetString();

        return string.Equals(status, "authenticated", StringComparison.OrdinalIgnoreCase)
            ? ReadTokens(root.GetProperty("user").GetProperty("tokens"))
            : throw new InvalidOperationException($"Unsupported test authentication status '{status}'.");
    }

    private static async Task<TestAuthenticationTokens> CompletePlatformAuthenticationAsync(
        this HttpClient client,
        HttpResponseMessage response,
        string _)
    {
        client.DefaultRequestHeaders.Remove("x-tenant-code");
        using var authentication = await ReadSuccessfulJsonAsync(response);
        var root = authentication.RootElement;
        var status = root.GetProperty("status").GetString();

        return string.Equals(status, "authenticated", StringComparison.OrdinalIgnoreCase)
            ? ReadTokens(root.GetProperty("user").GetProperty("tokens"))
            : throw new InvalidOperationException($"Unsupported test authentication status '{status}'.");
    }

    public static void UseAccessToken(this HttpClient client, TestAuthenticationTokens tokens)
    {
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
    }

    private static void SetTenantHeader(HttpClient client, Guid tenantId)
    {
        client.DefaultRequestHeaders.Remove("x-tenant-code");
        client.DefaultRequestHeaders.Add("x-tenant-code", tenantId.ToString("N"));
    }

    private static async Task<JsonDocument> ReadSuccessfulJsonAsync(HttpResponseMessage response)
    {
        var body = await response.Content.ReadAsStringAsync();
        if (!response.IsSuccessStatusCode)
            throw new HttpRequestException(
                $"Authentication request failed with {(int)response.StatusCode} ({response.StatusCode}): {body}");

        return JsonDocument.Parse(body);
    }

    private static TestAuthenticationTokens ReadTokens(JsonElement tokens)
    {
        var accessToken = tokens.GetProperty("accessToken").GetString()
                          ?? throw new InvalidOperationException(
                              "Authentication response did not contain an access token.");
        var refreshToken = tokens.GetProperty("refreshToken").GetString()
                           ?? throw new InvalidOperationException(
                               "Authentication response did not contain a refresh token.");
        return new TestAuthenticationTokens(accessToken, refreshToken);
    }
}