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

    public OrganizationsControllerTests(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 Organization Tests

    [Fact]
    public async Task CreateOrganization_ValidRequest_Returns201WithLocation()
    {
        // Arrange
        var request = new CreateOrganizationRequest
        {
            Name = $"Test Organization {Guid.NewGuid():N}",
            Slug = $"test-org-{Guid.NewGuid():N}".Substring(0, 30),
            Description = "A test organization"
        };

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

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

        var result = await response.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
        result.Should().NotBeNull();
        result!.Success.Should().BeTrue();
        result.Data.Should().NotBeNull();
        result.Data!.Name.Should().Be(request.Name);
        result.Data.Slug.Should().Be(request.Slug);
        result.Data.Status.Should().Be("Active");
    }

    [Fact]
    public async Task CreateOrganization_DuplicateSlug_Returns409Conflict()
    {
        // Arrange
        var uniqueSlug = $"duplicate-{Guid.NewGuid():N}".Substring(0, 30);
        var request = new CreateOrganizationRequest
        {
            Name = "Organization 1",
            Slug = uniqueSlug
        };

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

        // Second creation with same slug - should fail
        request.Name = "Organization 2"; // Different name, same slug

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

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

    [Fact]
    public async Task CreateOrganization_WithParentOrganization_Returns201WithParentId()
    {
        // Arrange - Create parent first
        var parentSlug = $"parent-{Guid.NewGuid():N}".Substring(0, 30);
        var parentRequest = new CreateOrganizationRequest
        {
            Name = "Parent Organization",
            Slug = parentSlug
        };
        var parentResponse = await _client.PostAsJsonAsync("/api/v1/organizations", parentRequest);
        var parentResult = await parentResponse.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
        var parentId = parentResult!.Data!.Id;

        // Create child
        var childSlug = $"child-{Guid.NewGuid():N}".Substring(0, 30);
        var childRequest = new CreateOrganizationRequest
        {
            Name = "Child Organization",
            Slug = childSlug,
            ParentOrganizationId = parentId
        };

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

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

    #endregion

    #region Get Organization Tests

    [Fact]
    public async Task GetOrganizationById_ExistingId_Returns200WithData()
    {
        // Arrange - Create organization first
        var slug = $"get-test-{Guid.NewGuid():N}".Substring(0, 30);
        var createRequest = new CreateOrganizationRequest
        {
            Name = "Get Test Organization",
            Slug = slug
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/organizations", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
        var orgId = createResult!.Data!.Id;

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

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        
        var result = await response.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
        result.Should().NotBeNull();
        result!.Success.Should().BeTrue();
        result.Data!.Id.Should().Be(orgId);
        result.Data.Name.Should().Be("Get Test Organization");
    }

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

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

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

    [Fact]
    public async Task GetOrganizationBySlug_ExistingSlug_Returns200()
    {
        // Arrange - Create organization first
        var slug = $"slug-test-{Guid.NewGuid():N}".Substring(0, 30);
        var createRequest = new CreateOrganizationRequest
        {
            Name = "Slug Test Organization",
            Slug = slug
        };
        await _client.PostAsJsonAsync("/api/v1/organizations", createRequest);

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

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        
        var result = await response.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
        result!.Data!.Slug.Should().Be(slug);
    }

    [Fact]
    public async Task GetOrganizationBySlug_NonExistingSlug_Returns404()
    {
        // Act
        var response = await _client.GetAsync("/api/v1/organizations/slug/non-existent-slug-12345");

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

    #endregion

    #region Update Organization Tests

    [Fact]
    public async Task UpdateOrganization_ValidRequest_Returns200()
    {
        // Arrange - Create organization first
        var slug = $"update-test-{Guid.NewGuid():N}".Substring(0, 30);
        var createRequest = new CreateOrganizationRequest
        {
            Name = "Original Name",
            Slug = slug,
            Description = "Original Description"
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/organizations", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
        var orgId = createResult!.Data!.Id;

        var updateRequest = new UpdateOrganizationRequest
        {
            Name = "Updated Name",
            Description = "Updated Description"
        };

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

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

    [Fact]
    public async Task UpdateOrganization_NonExistingId_Returns404()
    {
        // Arrange
        var updateRequest = new UpdateOrganizationRequest
        {
            Name = "Updated Name"
        };

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

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

    #endregion

    #region Archive Organization Tests

    [Fact]
    public async Task ArchiveOrganization_ExistingOrg_Returns200()
    {
        // Arrange - Create organization first
        var slug = $"archive-test-{Guid.NewGuid():N}".Substring(0, 30);
        var createRequest = new CreateOrganizationRequest
        {
            Name = "Archive Test Organization",
            Slug = slug
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/organizations", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
        var orgId = createResult!.Data!.Id;

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

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

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

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

    #endregion

    #region Hierarchy Tests

    [Fact]
    public async Task GetOrganizationHierarchy_ExistingOrg_Returns200()
    {
        // Arrange - Create organization first
        var slug = $"hierarchy-{Guid.NewGuid():N}".Substring(0, 30);
        var createRequest = new CreateOrganizationRequest
        {
            Name = "Hierarchy Organization",
            Slug = slug
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/organizations", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
        var orgId = createResult!.Data!.Id;

        // Act
        var response = await _client.GetAsync($"/api/v1/organizations/{orgId}/hierarchy");

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

    [Fact]
    public async Task GetChildOrganizations_ExistingOrg_Returns200()
    {
        // Arrange - Create parent organization first
        var slug = $"parent-child-{Guid.NewGuid():N}".Substring(0, 30);
        var createRequest = new CreateOrganizationRequest
        {
            Name = "Parent Organization",
            Slug = slug
        };
        var createResponse = await _client.PostAsJsonAsync("/api/v1/organizations", createRequest);
        var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
        var orgId = createResult!.Data!.Id;

        // Act
        var response = await _client.GetAsync($"/api/v1/organizations/{orgId}/children");

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

    #endregion

    #region Helper Methods

    /// <summary>
    /// EN: Generate a test JWT token for authentication.
    /// VI: Tạo JWT token test để xác thực.
    /// </summary>
    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
}
