using System.Net;
using System.Net.Http.Json;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
using Xunit;
using FluentAssertions;
using IamService.API.Application.Common;
using IamService.API.Controllers;

namespace IamService.FunctionalTests.Controllers;

/// <summary>
/// EN: Functional tests for RolesController endpoints.
/// VI: Functional tests cho các endpoints của RolesController.
/// </summary>
public class RolesControllerTests : IClassFixture<CustomWebApplicationFactory>
{
    private readonly HttpClient _client;
    private readonly CustomWebApplicationFactory _factory;

    public RolesControllerTests(CustomWebApplicationFactory factory)
    {
        _factory = factory;
        _client = factory.CreateClient();
        
        // EN: Add authorization header with test token
        // VI: Thêm authorization header với test token
        _client.DefaultRequestHeaders.Authorization = 
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", GenerateTestToken());
    }

    #region Create Role Tests

    [Fact]
    public async Task CreateRole_ValidRequest_Returns201()
    {
        // Arrange
        var request = new CreateRoleRequest
        {
            Name = $"TestRole_{Guid.NewGuid():N}",
            Description = "A test role"
        };

        // Act
        var response = await _client.PostAsJsonAsync("/api/v1/roles", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Created);
        response.Headers.Location.Should().NotBeNull();

        var result = await response.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
        result.Should().NotBeNull();
        result!.Success.Should().BeTrue();
        result.Data.Should().NotBeNull();
        result.Data!.Name.Should().Be(request.Name);
        result.Data.Description.Should().Be(request.Description);
    }

    [Fact]
    public async Task CreateRole_WithoutDescription_Returns201()
    {
        // Arrange
        var request = new CreateRoleRequest
        {
            Name = $"NoDescRole_{Guid.NewGuid():N}"
        };

        // Act
        var response = await _client.PostAsJsonAsync("/api/v1/roles", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Created);
        
        var result = await response.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
        result!.Data!.Description.Should().BeNull();
    }

    [Fact]
    public async Task CreateRole_DuplicateName_Returns409()
    {
        // Arrange
        var uniqueName = $"DupRole_{Guid.NewGuid():N}";
        var request = new CreateRoleRequest { Name = uniqueName };

        // First creation - should succeed
        var firstResponse = await _client.PostAsJsonAsync("/api/v1/roles", request);
        firstResponse.StatusCode.Should().Be(HttpStatusCode.Created);

        // Second creation with same name - should fail
        // Act
        var response = await _client.PostAsJsonAsync("/api/v1/roles", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Conflict);
        
        var result = await response.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
        result!.Success.Should().BeFalse();
        result.Error!.Code.Should().Be("ROLE_EXISTS");
    }

    #endregion

    #region Get Role Tests

    [Fact]
    public async Task GetRoleById_ExistingId_Returns200()
    {
        // Arrange - Create role first
        var createRequest = new CreateRoleRequest
        {
            Name = $"GetRole_{Guid.NewGuid():N}"
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/roles", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
        var roleId = createResult!.Data!.Id;

        // Act
        var response = await _client.GetAsync($"/api/v1/roles/{roleId}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        
        var result = await response.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
        result!.Data!.Id.Should().Be(roleId);
        result.Data.Name.Should().Be(createRequest.Name);
    }

    [Fact]
    public async Task GetRoleById_NonExistingId_Returns404()
    {
        // Arrange
        var nonExistentId = Guid.NewGuid();

        // Act
        var response = await _client.GetAsync($"/api/v1/roles/{nonExistentId}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
        
        var result = await response.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
        result!.Error!.Code.Should().Be("ROLE_NOT_FOUND");
    }

    [Fact]
    public async Task GetRoles_Returns200WithPagination()
    {
        // Arrange - Create at least one role
        var createRequest = new CreateRoleRequest
        {
            Name = $"ListRole_{Guid.NewGuid():N}"
        };
        await _client.PostAsJsonAsync("/api/v1/roles", createRequest);

        // Act
        var response = await _client.GetAsync("/api/v1/roles?pageNumber=1&pageSize=10");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        
        var result = await response.Content.ReadFromJsonAsync<ApiResponse<IEnumerable<RoleResponse>>>();
        result!.Success.Should().BeTrue();
        result.Pagination.Should().NotBeNull();
        result.Pagination!.PageNumber.Should().Be(1);
    }

    #endregion

    #region Update Role Tests

    [Fact]
    public async Task UpdateRole_ValidRequest_Returns200()
    {
        // Arrange - Create role first
        var createRequest = new CreateRoleRequest
        {
            Name = $"UpdateRole_{Guid.NewGuid():N}",
            Description = "Original description"
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/roles", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
        var roleId = createResult!.Data!.Id;

        var updateRequest = new UpdateRoleRequest
        {
            Name = $"UpdatedRole_{Guid.NewGuid():N}",
            Description = "Updated description"
        };

        // Act
        var response = await _client.PutAsJsonAsync($"/api/v1/roles/{roleId}", updateRequest);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        
        var result = await response.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
        result!.Data!.Name.Should().Be(updateRequest.Name);
        result.Data.Description.Should().Be(updateRequest.Description);
    }

    [Fact]
    public async Task UpdateRole_NonExistingId_Returns404()
    {
        // Arrange
        var updateRequest = new UpdateRoleRequest
        {
            Name = "UpdatedRole"
        };

        // Act
        var response = await _client.PutAsJsonAsync($"/api/v1/roles/{Guid.NewGuid()}", updateRequest);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    #endregion

    #region Delete Role Tests

    [Fact]
    public async Task DeleteRole_ExistingRole_Returns200()
    {
        // Arrange - Create role first
        var createRequest = new CreateRoleRequest
        {
            Name = $"DeleteRole_{Guid.NewGuid():N}"
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/roles", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
        var roleId = createResult!.Data!.Id;

        // Act
        var response = await _client.DeleteAsync($"/api/v1/roles/{roleId}");

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

    [Fact]
    public async Task DeleteRole_NonExistingId_Returns404()
    {
        // Act
        var response = await _client.DeleteAsync($"/api/v1/roles/{Guid.NewGuid()}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    #endregion

    #region Assign/Remove Role Tests

    [Fact]
    public async Task AssignRoleToUser_ValidRequest_Returns200()
    {
        // Arrange - Create role first
        var roleName = $"AssignRole_{Guid.NewGuid():N}";
        await _client.PostAsJsonAsync("/api/v1/roles", new CreateRoleRequest { Name = roleName });

        // Create user via registration
        var registerRequest = new
        {
            Email = $"assign-test-{Guid.NewGuid():N}@example.com",
            Password = "Test123!",
            FirstName = "Test",
            LastName = "User"
        };
        var registerResponse = await _client.PostAsJsonAsync("/api/v1/auth/register", registerRequest);
        if (!registerResponse.IsSuccessStatusCode)
        {
            // Skip if can't register user 
            return;
        }
        
        // Get user ID from response - may vary based on response structure
        var userId = Guid.NewGuid(); // Placeholder if user creation works differently

        var assignRequest = new AssignRoleRequest { RoleName = roleName };

        // Act
        var response = await _client.PostAsJsonAsync($"/api/v1/users/{userId}/roles", assignRequest);

        // Assert - Accept 200 or 404 (if user registration flow is different)
        response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.NotFound);
    }

    #endregion

    #region Helper Methods

    private static string GenerateTestToken()
    {
        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Email, "test@example.com"),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim("name", "Test User")
        };

        var key = new SymmetricSecurityKey("test-secret-key-for-testing-purposes-only-32-chars!"u8.ToArray());
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: "http://localhost",
            audience: "api",
            claims: claims,
            expires: DateTime.UtcNow.AddHours(1),
            signingCredentials: creds
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    #endregion
}
