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 GroupsController endpoints.
/// VI: Functional tests cho các endpoints của GroupsController.
/// </summary>
public class GroupsControllerTests : IClassFixture<CustomWebApplicationFactory>
{
    private readonly HttpClient _client;
    private readonly CustomWebApplicationFactory _factory;
    private readonly Guid _testOrganizationId;

    public GroupsControllerTests(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());
        
        // EN: Create a test organization for all group tests
        // VI: Tạo organization test cho tất cả group tests
        _testOrganizationId = CreateTestOrganizationAsync().GetAwaiter().GetResult();
    }

    #region Create Group Tests

    [Fact]
    public async Task CreateGroup_ValidRequest_Returns201WithLocation()
    {
        // Arrange
        var request = new CreateGroupRequest
        {
            Name = $"Test Group {Guid.NewGuid():N}",
            OrganizationId = _testOrganizationId,
            Description = "A test group"
        };

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

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

        var result = await response.Content.ReadFromJsonAsync<ApiResponse<GroupResponse>>();
        result.Should().NotBeNull();
        result!.Success.Should().BeTrue();
        result.Data.Should().NotBeNull();
        result.Data!.Name.Should().Be(request.Name);
        result.Data.OrganizationId.Should().Be(_testOrganizationId);
        result.Data.MemberCount.Should().Be(0);
    }

    [Fact]
    public async Task CreateGroup_WithoutDescription_Returns201()
    {
        // Arrange
        var request = new CreateGroupRequest
        {
            Name = $"No Description Group {Guid.NewGuid():N}",
            OrganizationId = _testOrganizationId
        };

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

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

    [Fact]
    public async Task CreateGroup_NonExistentOrganization_Returns400()
    {
        // Arrange
        var request = new CreateGroupRequest
        {
            Name = "Group for Non-existent Org",
            OrganizationId = Guid.NewGuid() // Non-existent org
        };

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

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

    #endregion

    #region Get Group Tests

    [Fact]
    public async Task GetGroupById_ExistingId_Returns200WithData()
    {
        // Arrange - Create group first
        var createRequest = new CreateGroupRequest
        {
            Name = $"Get Test Group {Guid.NewGuid():N}",
            OrganizationId = _testOrganizationId
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/groups", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<GroupResponse>>();
        var groupId = createResult!.Data!.Id;

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

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        
        var result = await response.Content.ReadFromJsonAsync<ApiResponse<GroupResponse>>();
        result!.Success.Should().BeTrue();
        result.Data!.Id.Should().Be(groupId);
    }

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

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

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

    [Fact]
    public async Task GetGroupsByOrganization_ExistingOrg_Returns200()
    {
        // Arrange - Create some groups
        var groupName = $"Org Group {Guid.NewGuid():N}";
        var createRequest = new CreateGroupRequest
        {
            Name = groupName,
            OrganizationId = _testOrganizationId
        };
        await _client.PostAsJsonAsync("/api/v1/groups", createRequest);

        // Act
        var response = await _client.GetAsync($"/api/v1/groups?organizationId={_testOrganizationId}");

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

    #endregion

    #region Add Member Tests

    [Fact]
    public async Task AddMember_ValidRequest_Returns200()
    {
        // Arrange - Create group first
        var createRequest = new CreateGroupRequest
        {
            Name = $"Member Test Group {Guid.NewGuid():N}",
            OrganizationId = _testOrganizationId
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/groups", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<GroupResponse>>();
        var groupId = createResult!.Data!.Id;

        var addMemberRequest = new AddGroupMemberRequest
        {
            UserId = Guid.NewGuid()
        };

        // Act
        var response = await _client.PostAsJsonAsync($"/api/v1/groups/{groupId}/members", addMemberRequest);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        
        var result = await response.Content.ReadFromJsonAsync<ApiResponse<GroupMemberResponse>>();
        result!.Success.Should().BeTrue();
        result.Data!.GroupId.Should().Be(groupId);
        result.Data.UserId.Should().Be(addMemberRequest.UserId);
        result.Data.Role.Should().Be("Member"); // Default role
    }

    [Fact]
    public async Task AddMember_WithAdminRole_Returns200WithAdminRole()
    {
        // Arrange - Create group first
        var createRequest = new CreateGroupRequest
        {
            Name = $"Admin Role Test Group {Guid.NewGuid():N}",
            OrganizationId = _testOrganizationId
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/groups", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<GroupResponse>>();
        var groupId = createResult!.Data!.Id;

        var addMemberRequest = new AddGroupMemberRequest
        {
            UserId = Guid.NewGuid(),
            RoleId = 2 // Admin role
        };

        // Act
        var response = await _client.PostAsJsonAsync($"/api/v1/groups/{groupId}/members", addMemberRequest);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        
        var result = await response.Content.ReadFromJsonAsync<ApiResponse<GroupMemberResponse>>();
        result!.Data!.Role.Should().Be("Admin");
    }

    [Fact]
    public async Task AddMember_GroupNotFound_Returns404()
    {
        // Arrange
        var addMemberRequest = new AddGroupMemberRequest
        {
            UserId = Guid.NewGuid()
        };

        // Act
        var response = await _client.PostAsJsonAsync($"/api/v1/groups/{Guid.NewGuid()}/members", addMemberRequest);

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

    [Fact]
    public async Task AddMember_DuplicateMember_Returns400()
    {
        // Arrange - Create group and add member first
        var createRequest = new CreateGroupRequest
        {
            Name = $"Duplicate Member Test Group {Guid.NewGuid():N}",
            OrganizationId = _testOrganizationId
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/groups", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<GroupResponse>>();
        var groupId = createResult!.Data!.Id;

        var userId = Guid.NewGuid();
        var addMemberRequest = new AddGroupMemberRequest { UserId = userId };
        await _client.PostAsJsonAsync($"/api/v1/groups/{groupId}/members", addMemberRequest);

        // Act - Try to add same member again
        var response = await _client.PostAsJsonAsync($"/api/v1/groups/{groupId}/members", addMemberRequest);

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

    #endregion

    #region Remove Member Tests

    [Fact]
    public async Task RemoveMember_ExistingMember_Returns200()
    {
        // Arrange - Create group and add member
        var createRequest = new CreateGroupRequest
        {
            Name = $"Remove Member Test Group {Guid.NewGuid():N}",
            OrganizationId = _testOrganizationId
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/groups", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<GroupResponse>>();
        var groupId = createResult!.Data!.Id;

        var userId = Guid.NewGuid();
        await _client.PostAsJsonAsync($"/api/v1/groups/{groupId}/members", new AddGroupMemberRequest { UserId = userId });

        // Act
        var response = await _client.DeleteAsync($"/api/v1/groups/{groupId}/members/{userId}");

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

    [Fact]
    public async Task RemoveMember_NonExistingMember_Returns404()
    {
        // Arrange - Create group
        var createRequest = new CreateGroupRequest
        {
            Name = $"Remove Non-Existing Member Test {Guid.NewGuid():N}",
            OrganizationId = _testOrganizationId
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/groups", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<GroupResponse>>();
        var groupId = createResult!.Data!.Id;

        // Act - Try to remove non-existing member
        var response = await _client.DeleteAsync($"/api/v1/groups/{groupId}/members/{Guid.NewGuid()}");

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

    #endregion

    #region Delete Group Tests

    [Fact]
    public async Task DeleteGroup_ExistingGroup_Returns200()
    {
        // Arrange - Create group first
        var createRequest = new CreateGroupRequest
        {
            Name = $"Delete Test Group {Guid.NewGuid():N}",
            OrganizationId = _testOrganizationId
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/groups", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<GroupResponse>>();
        var groupId = createResult!.Data!.Id;

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

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

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

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

    #endregion

    #region Helper Methods

    private async Task<Guid> CreateTestOrganizationAsync()
    {
        var slug = $"test-org-{Guid.NewGuid():N}".Substring(0, 30);
        var request = new CreateOrganizationRequest
        {
            Name = "Test Organization for Groups",
            Slug = slug
        };

        var response = await _client.PostAsJsonAsync("/api/v1/organizations", request);
        var result = await response.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
        return result?.Data?.Id ?? Guid.NewGuid();
    }

    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
}
