using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using ColaFlow.Modules.ProjectManagement.IntegrationTests.Infrastructure;
using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence;
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
using ColaFlow.Modules.ProjectManagement.Application.Queries.AuditLogs;
using Microsoft.EntityFrameworkCore;

namespace ColaFlow.Modules.ProjectManagement.IntegrationTests;

/// <summary>
/// Integration tests for Audit Log Query API (Sprint 2 Story 2 Task 5)
/// Tests the REST API endpoints for querying audit history
/// </summary>
public class AuditLogQueryApiTests : IClassFixture<PMWebApplicationFactory>
{
    private readonly PMWebApplicationFactory _factory;
    private readonly HttpClient _client;

    private readonly Guid _tenantId = Guid.NewGuid();
    private readonly Guid _userId = Guid.NewGuid();

    public AuditLogQueryApiTests(PMWebApplicationFactory factory)
    {
        _factory = factory;
        _client = _factory.CreateClient();

        var token = TestAuthHelper.GenerateJwtToken(_userId, _tenantId, "test-tenant", "user@test.com");
        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
    }

    [Fact]
    public async Task GetAuditLogById_ShouldReturnAuditLog()
    {
        // Arrange: Create a project (which generates an audit log)
        var projectResponse = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Test Project",
            Key = "TPRO",
            Description = "Test"
        });
        var project = await projectResponse.Content.ReadFromJsonAsync<ProjectDto>();

        // Get the audit log ID directly from database
        using var scope = _factory.Services.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<PMDbContext>();
        var auditLog = await context.AuditLogs
            .IgnoreQueryFilters()
            .Where(a => a.EntityType == "Project" && a.EntityId == project!.Id && a.Action == "Create")
            .FirstOrDefaultAsync();

        // Act: Get audit log by ID via API
        var response = await _client.GetAsync($"/api/v1/auditlogs/{auditLog!.Id}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var result = await response.Content.ReadFromJsonAsync<AuditLogDto>();

        result.Should().NotBeNull();
        result!.Id.Should().Be(auditLog.Id);
        result.EntityType.Should().Be("Project");
        result.EntityId.Should().Be(project!.Id);
        result.Action.Should().Be("Create");
        result.UserId.Should().Be(_userId);
        result.NewValues.Should().NotBeNullOrEmpty();
    }

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

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

    [Fact]
    public async Task GetAuditLogsByEntity_ShouldReturnEntityHistory()
    {
        // Arrange: Create and update a project
        var createResponse = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Original Name",
            Key = "ORIG",
            Description = "Original description"
        });
        var project = await createResponse.Content.ReadFromJsonAsync<ProjectDto>();
        var projectId = project!.Id;

        await _client.PutAsJsonAsync($"/api/v1/projects/{projectId}", new
        {
            Name = "Updated Name",
            Description = "Updated description"
        });

        // Act: Get audit history for the project
        var response = await _client.GetAsync($"/api/v1/auditlogs/entity/Project/{projectId}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var auditLogs = await response.Content.ReadFromJsonAsync<List<AuditLogDto>>();

        auditLogs.Should().NotBeNull();
        auditLogs.Should().HaveCount(2); // Create + Update
        auditLogs.Should().Contain(a => a.Action == "Create");
        auditLogs.Should().Contain(a => a.Action == "Update");
        auditLogs.Should().AllSatisfy(a =>
        {
            a.EntityType.Should().Be("Project");
            a.EntityId.Should().Be(projectId);
            a.UserId.Should().Be(_userId);
        });
    }

    [Fact]
    public async Task GetAuditLogsByEntity_ShouldOnlyReturnChangedFields()
    {
        // Arrange: Create a project
        var createResponse = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Original Name",
            Key = "ORIG2",
            Description = "Original description"
        });
        var project = await createResponse.Content.ReadFromJsonAsync<ProjectDto>();
        var projectId = project!.Id;

        // Update only the Name field
        await _client.PutAsJsonAsync($"/api/v1/projects/{projectId}", new
        {
            Name = "Updated Name",
            Description = "Original description" // Same as before
        });

        // Act: Get audit history
        var response = await _client.GetAsync($"/api/v1/auditlogs/entity/Project/{projectId}");
        var auditLogs = await response.Content.ReadFromJsonAsync<List<AuditLogDto>>();

        // Assert: Update log should only contain changed fields
        var updateLog = auditLogs!.First(a => a.Action == "Update");
        updateLog.OldValues.Should().NotBeNullOrEmpty();
        updateLog.NewValues.Should().NotBeNullOrEmpty();

        // NewValues should only contain "Name" field (not Description)
        updateLog.NewValues.Should().Contain("Name");
        updateLog.NewValues.Should().Contain("Updated Name");

        // Should NOT contain unchanged "Description" field (Phase 2 optimization)
        updateLog.NewValues.Should().NotContain("Original description");
    }

    [Fact]
    public async Task GetAuditLogsByEntity_DifferentTenant_ShouldReturnEmpty()
    {
        // Arrange: Tenant 1 creates a project
        var tenant1Id = Guid.NewGuid();
        var tenant1Token = TestAuthHelper.GenerateJwtToken(_userId, tenant1Id, "tenant1", "user@tenant1.com");
        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tenant1Token);

        var createResponse = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Tenant 1 Project",
            Key = "T1PRO",
            Description = "Test"
        });
        var project = await createResponse.Content.ReadFromJsonAsync<ProjectDto>();
        var projectId = project!.Id;

        // Act: Tenant 2 tries to access the audit history
        var tenant2Id = Guid.NewGuid();
        var tenant2Token = TestAuthHelper.GenerateJwtToken(_userId, tenant2Id, "tenant2", "user@tenant2.com");
        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tenant2Token);

        var response = await _client.GetAsync($"/api/v1/auditlogs/entity/Project/{projectId}");

        // Assert: Tenant 2 should not see Tenant 1's audit logs
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var auditLogs = await response.Content.ReadFromJsonAsync<List<AuditLogDto>>();
        auditLogs.Should().NotBeNull();
        auditLogs.Should().BeEmpty("Different tenant should not see other tenant's audit logs");
    }

    [Fact]
    public async Task GetRecentAuditLogs_ShouldReturnRecentLogs()
    {
        // Arrange: Create multiple projects
        for (int i = 0; i < 5; i++)
        {
            await _client.PostAsJsonAsync("/api/v1/projects", new
            {
                Name = $"Project {i}",
                Key = $"P{i}",
                Description = "Test"
            });
        }

        // Act: Get recent audit logs (default count = 100)
        var response = await _client.GetAsync("/api/v1/auditlogs/recent");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var auditLogs = await response.Content.ReadFromJsonAsync<List<AuditLogDto>>();

        auditLogs.Should().NotBeNull();
        auditLogs.Should().HaveCountGreaterOrEqualTo(5); // At least the 5 we just created
        auditLogs.Should().AllSatisfy(a => a.UserId.Should().Be(_userId));
    }

    [Fact]
    public async Task GetRecentAuditLogs_WithCountLimit_ShouldRespectLimit()
    {
        // Arrange: Create multiple projects
        for (int i = 0; i < 10; i++)
        {
            await _client.PostAsJsonAsync("/api/v1/projects", new
            {
                Name = $"Project Limit {i}",
                Key = $"PL{i}",
                Description = "Test"
            });
        }

        // Act: Get recent audit logs with limit of 5
        var response = await _client.GetAsync("/api/v1/auditlogs/recent?count=5");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var auditLogs = await response.Content.ReadFromJsonAsync<List<AuditLogDto>>();

        auditLogs.Should().NotBeNull();
        auditLogs.Should().HaveCount(5, "API should respect the count limit");
    }

    [Fact]
    public async Task GetRecentAuditLogs_ExceedMaxLimit_ShouldCapAt1000()
    {
        // Act: Request more than max allowed (1000)
        var response = await _client.GetAsync("/api/v1/auditlogs/recent?count=5000");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var auditLogs = await response.Content.ReadFromJsonAsync<List<AuditLogDto>>();

        auditLogs.Should().NotBeNull();
        auditLogs!.Count.Should().BeLessOrEqualTo(1000, "API should cap count at max 1000");
    }

    [Fact]
    public async Task GetRecentAuditLogs_DifferentTenant_ShouldOnlyShowOwnLogs()
    {
        // Arrange: Tenant 1 creates a project
        var tenant1Id = Guid.NewGuid();
        var tenant1Token = TestAuthHelper.GenerateJwtToken(_userId, tenant1Id, "tenant1", "user1@test.com");
        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tenant1Token);

        await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Tenant 1 Recent Project",
            Key = "T1REC",
            Description = "Test"
        });

        // Act: Tenant 2 gets recent logs
        var tenant2Id = Guid.NewGuid();
        var tenant2Token = TestAuthHelper.GenerateJwtToken(_userId, tenant2Id, "tenant2", "user2@test.com");
        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tenant2Token);

        var response = await _client.GetAsync("/api/v1/auditlogs/recent");

        // Assert: Tenant 2 should NOT see Tenant 1's audit logs
        var auditLogs = await response.Content.ReadFromJsonAsync<List<AuditLogDto>>();
        auditLogs.Should().NotBeNull();

        // Verify no logs belong to tenant1 by checking none have tenant1's project
        using var scope = _factory.Services.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<PMDbContext>();
        var tenant1Logs = await context.AuditLogs
            .IgnoreQueryFilters()
            .Where(a => a.TenantId.Value == tenant1Id)
            .ToListAsync();

        var tenant1LogIds = tenant1Logs.Select(a => a.Id).ToList();
        auditLogs.Should().NotContain(a => tenant1LogIds.Contains(a.Id),
            "Tenant 2 should not see Tenant 1's audit logs");
    }

    [Fact]
    public async Task AuditLog_ShouldCaptureUserId()
    {
        // Arrange: Create a project with specific user
        var specificUserId = Guid.NewGuid();
        var token = TestAuthHelper.GenerateJwtToken(specificUserId, _tenantId, "test-tenant", "specific@test.com");
        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

        var createResponse = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "User ID Test Project",
            Key = "UIDTP",
            Description = "Test user capture"
        });
        var project = await createResponse.Content.ReadFromJsonAsync<ProjectDto>();

        // Act: Get audit logs
        var response = await _client.GetAsync($"/api/v1/auditlogs/entity/Project/{project!.Id}");
        var auditLogs = await response.Content.ReadFromJsonAsync<List<AuditLogDto>>();

        // Assert: UserId should match the specific user
        var createLog = auditLogs!.First(a => a.Action == "Create");
        createLog.UserId.Should().Be(specificUserId, "Audit log should capture the user who performed the action");
    }

    [Fact]
    public async Task AuditLog_CreateAction_ShouldHaveNewValuesOnly()
    {
        // Arrange & Act: Create a project
        var createResponse = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Create Test Project",
            Key = "CTP",
            Description = "Test create audit"
        });
        var project = await createResponse.Content.ReadFromJsonAsync<ProjectDto>();

        var response = await _client.GetAsync($"/api/v1/auditlogs/entity/Project/{project!.Id}");
        var auditLogs = await response.Content.ReadFromJsonAsync<List<AuditLogDto>>();

        // Assert
        var createLog = auditLogs!.First(a => a.Action == "Create");
        createLog.NewValues.Should().NotBeNullOrEmpty("Create action should have NewValues");
        createLog.OldValues.Should().BeNullOrEmpty("Create action should NOT have OldValues");
    }

    [Fact]
    public async Task AuditLog_DeleteAction_ShouldHaveOldValuesOnly()
    {
        // Arrange: Create and delete a project
        var createResponse = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Delete Test Project",
            Key = "DTP",
            Description = "Test delete audit"
        });
        var project = await createResponse.Content.ReadFromJsonAsync<ProjectDto>();
        var projectId = project!.Id;

        await _client.DeleteAsync($"/api/v1/projects/{projectId}");

        // Act
        var response = await _client.GetAsync($"/api/v1/auditlogs/entity/Project/{projectId}");
        var auditLogs = await response.Content.ReadFromJsonAsync<List<AuditLogDto>>();

        // Assert
        var deleteLog = auditLogs!.First(a => a.Action == "Delete");
        deleteLog.OldValues.Should().NotBeNullOrEmpty("Delete action should have OldValues");
        deleteLog.NewValues.Should().BeNullOrEmpty("Delete action should NOT have NewValues");
    }
}
