using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using ColaFlow.Modules.Identity.IntegrationTests.Infrastructure;
using FluentAssertions;

namespace ColaFlow.Modules.Identity.IntegrationTests.Identity;

/// <summary>
/// Integration tests for Role-Based Access Control (RBAC) functionality (Day 5 - Phase 2)
/// Tests role assignment, JWT claims, and role persistence across authentication flows
/// </summary>
public class RbacTests(DatabaseFixture fixture) : IClassFixture<DatabaseFixture>
{
    private readonly HttpClient _client = fixture.Client;

    [Fact]
    public async Task RegisterTenant_ShouldAssignTenantOwnerRole()
    {
        // Arrange & Act
        var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);

        // Assert - Verify token contains TenantOwner role
        TestAuthHelper.HasRole(accessToken, "TenantOwner").Should().BeTrue();
    }

    [Fact]
    public async Task RegisterTenant_ShouldIncludeRoleInJwtClaims()
    {
        // Arrange & Act
        var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);

        // Assert - Decode JWT and verify claims
        var handler = new JwtSecurityTokenHandler();
        var token = handler.ReadJwtToken(accessToken);
        var claims = token.Claims.ToList();

        // Should have either 'role' or 'tenant_role' claim with value 'TenantOwner'
        claims.Should().Contain(c =>
            (c.Type == "role" || c.Type == "tenant_role") &&
            c.Value == "TenantOwner");
    }

    [Fact]
    public async Task Login_ShouldPreserveRole()
    {
        // Arrange - Register tenant
        var email = $"admin-{Guid.NewGuid():N}@test.com";
        var tenantSlug = $"test-{Guid.NewGuid():N}";
        var password = "Admin@1234";

        await RegisterTenantAsync(tenantSlug, email, password);

        // Act - Login
        var (accessToken, _) = await TestAuthHelper.LoginAndGetTokensAsync(_client, tenantSlug, email, password);

        // Assert - Role should be preserved
        TestAuthHelper.HasRole(accessToken, "TenantOwner").Should().BeTrue();
    }

    [Fact]
    public async Task RefreshToken_ShouldPreserveRole()
    {
        // Arrange - Register and get initial tokens
        var (_, refreshToken) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);

        // Act - Refresh token
        var refreshRequest = new { refreshToken };
        var response = await _client.PostAsJsonAsync("/api/auth/refresh", refreshRequest);
        var result = await response.Content.ReadFromJsonAsync<RefreshResponse>();

        // Assert - New token should preserve role
        TestAuthHelper.HasRole(result!.AccessToken, "TenantOwner").Should().BeTrue();
    }

    [Fact]
    public async Task GetMe_ShouldReturnUserRoleInformation()
    {
        // Arrange - Register and get tokens
        var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);

        // Act - Call /api/auth/me with token
        _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        var response = await _client.GetAsync("/api/auth/me");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);

        var userInfo = await response.Content.ReadFromJsonAsync<UserInfoResponse>();
        userInfo.Should().NotBeNull();
        userInfo!.TenantRole.Should().Be("TenantOwner");
    }

    [Fact]
    public async Task JwtToken_ShouldContainAllRequiredRoleClaims()
    {
        // Arrange & Act
        var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);

        // Assert - Verify all expected claims
        var claims = TestAuthHelper.ParseJwtToken(accessToken).ToList();

        // Must have user identity claims
        claims.Should().Contain(c => c.Type == "user_id");
        claims.Should().Contain(c => c.Type == "tenant_id");
        claims.Should().Contain(c => c.Type == "email");
        claims.Should().Contain(c => c.Type == "full_name");

        // Must have role claim
        claims.Should().Contain(c =>
            (c.Type == "role" || c.Type == "tenant_role") &&
            c.Value == "TenantOwner");
    }

    [Fact]
    public async Task MultipleTokenRefresh_ShouldMaintainRole()
    {
        // Arrange - Register and get initial tokens
        var (_, refreshToken) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);

        // Act & Assert - Refresh multiple times
        for (int i = 0; i < 3; i++)
        {
            var response = await _client.PostAsJsonAsync("/api/auth/refresh", new { refreshToken });
            var result = await response.Content.ReadFromJsonAsync<RefreshResponse>();

            // Verify role is maintained
            TestAuthHelper.HasRole(result!.AccessToken, "TenantOwner").Should().BeTrue();

            // Update token for next iteration
            refreshToken = result.RefreshToken;
        }
    }

    [Fact]
    public async Task AccessProtectedEndpoint_WithValidRole_ShouldSucceed()
    {
        // Arrange - Register and get token with TenantOwner role
        var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);

        // Act - Access protected endpoint
        _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        var response = await _client.GetAsync("/api/auth/me");

        // Assert - Should succeed because user has valid role
        response.StatusCode.Should().Be(HttpStatusCode.OK);
    }

    [Fact]
    public async Task AccessProtectedEndpoint_WithoutToken_ShouldFail()
    {
        // Arrange - No authorization header

        // Act - Try to access protected endpoint
        var response = await _client.GetAsync("/api/auth/me");

        // Assert - Should fail with 401 Unauthorized
        response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
    }

    [Fact]
    public async Task AccessProtectedEndpoint_WithInvalidToken_ShouldFail()
    {
        // Arrange - Invalid token
        var invalidToken = "invalid.jwt.token";
        _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", invalidToken);

        // Act - Try to access protected endpoint
        var response = await _client.GetAsync("/api/auth/me");

        // Assert - Should fail with 401 Unauthorized
        response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
    }

    [Fact]
    public async Task RoleInformation_ShouldBeConsistentAcrossAllFlows()
    {
        // This test verifies role consistency across:
        // 1. Registration
        // 2. Login
        // 3. Token Refresh
        // 4. User Info Endpoint

        var email = $"admin-{Guid.NewGuid():N}@test.com";
        var tenantSlug = $"test-{Guid.NewGuid():N}";
        var password = "Admin@1234";

        // Step 1: Register
        await RegisterTenantAsync(tenantSlug, email, password);
        var (registerToken, _) = await TestAuthHelper.LoginAndGetTokensAsync(_client, tenantSlug, email, password);
        TestAuthHelper.HasRole(registerToken, "TenantOwner").Should().BeTrue("Registration should assign TenantOwner");

        // Step 2: Login
        var (loginToken, refreshToken) = await TestAuthHelper.LoginAndGetTokensAsync(_client, tenantSlug, email, password);
        TestAuthHelper.HasRole(loginToken, "TenantOwner").Should().BeTrue("Login should preserve TenantOwner");

        // Step 3: Token Refresh
        var refreshResponse = await _client.PostAsJsonAsync("/api/auth/refresh", new { refreshToken });
        var refreshResult = await refreshResponse.Content.ReadFromJsonAsync<RefreshResponse>();
        TestAuthHelper.HasRole(refreshResult!.AccessToken, "TenantOwner").Should().BeTrue("Refresh should preserve TenantOwner");

        // Step 4: User Info Endpoint
        _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", refreshResult.AccessToken);
        var meResponse = await _client.GetAsync("/api/auth/me");
        var userInfo = await meResponse.Content.ReadFromJsonAsync<UserInfoResponse>();
        userInfo!.TenantRole.Should().Be("TenantOwner", "User info should show TenantOwner");
    }

    #region Helper Methods

    private async Task RegisterTenantAsync(string tenantSlug, string email, string password)
    {
        var request = new
        {
            tenantName = "Test Corp",
            tenantSlug,
            subscriptionPlan = "Professional",
            adminEmail = email,
            adminPassword = password,
            adminFullName = "Test Admin"
        };

        var response = await _client.PostAsJsonAsync("/api/tenants/register", request);
        response.EnsureSuccessStatusCode();
    }

    #endregion
}
