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.Domain.Entities;
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
using Microsoft.EntityFrameworkCore;

namespace ColaFlow.Modules.ProjectManagement.IntegrationTests;

/// <summary>
/// Integration tests for AuditInterceptor
/// Verifies that all Create/Update/Delete operations are automatically logged
/// </summary>
public class AuditInterceptorTests : IClassFixture<PMWebApplicationFactory>
{
    private readonly PMWebApplicationFactory _factory;
    private readonly HttpClient _client;

    // Test tenant and user IDs
    private readonly Guid _tenantId = Guid.NewGuid();
    private readonly Guid _userId = Guid.NewGuid();

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

        // Set up authentication
        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 CreateProject_Should_CreateAuditLog()
    {
        // Arrange
        var createRequest = new
        {
            Name = "Test Project for Audit",
            Key = "TPFA",
            Description = "Testing audit logging"
        };

        // Act
        var createResponse = await _client.PostAsJsonAsync("/api/v1/projects", createRequest);
        createResponse.EnsureSuccessStatusCode();

        var project = await createResponse.Content.ReadFromJsonAsync<ProjectDto>();
        var projectId = project!.Id;

        // Assert: Check audit log was created
        using var scope = _factory.Services.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<PMDbContext>();

        var auditLog = await context.AuditLogs
            .IgnoreQueryFilters() // Bypass tenant filter when querying directly in tests
            .Where(a => a.EntityType == "Project" && a.EntityId == projectId && a.Action == "Create")
            .FirstOrDefaultAsync();

        auditLog.Should().NotBeNull();
        auditLog!.Action.Should().Be("Create");
        auditLog.EntityType.Should().Be("Project");
        auditLog.EntityId.Should().Be(projectId);
        auditLog.TenantId.Value.Should().Be(_tenantId);
        auditLog.UserId.Should().NotBeNull();
        auditLog.UserId!.Value.Should().Be(_userId);
        auditLog.Timestamp.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

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

        // Act: Update the project
        var updateResponse = await _client.PutAsJsonAsync($"/api/v1/projects/{projectId}", new
        {
            Name = "Updated Project Name",
            Description = "Updated description"
        });
        updateResponse.EnsureSuccessStatusCode();

        // Assert: Check update audit log was created
        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 == projectId && a.Action == "Update")
            .FirstOrDefaultAsync();

        auditLog.Should().NotBeNull();
        auditLog!.Action.Should().Be("Update");
        auditLog.EntityType.Should().Be("Project");
        auditLog.EntityId.Should().Be(projectId);
        auditLog.TenantId.Value.Should().Be(_tenantId);
        auditLog.UserId.Should().NotBeNull();
        auditLog.UserId!.Value.Should().Be(_userId);
    }

    [Fact]
    public async Task DeleteProject_Should_CreateAuditLog()
    {
        // Arrange: Create a project first
        var createResponse = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Project To Delete",
            Key = "PTDEL",
            Description = "This project will be deleted"
        });
        var project = await createResponse.Content.ReadFromJsonAsync<ProjectDto>();
        var projectId = project!.Id;

        // Act: Delete the project
        var deleteResponse = await _client.DeleteAsync($"/api/v1/projects/{projectId}");
        deleteResponse.EnsureSuccessStatusCode();

        // Assert: Check delete audit log was created
        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 == projectId && a.Action == "Delete")
            .FirstOrDefaultAsync();

        auditLog.Should().NotBeNull();
        auditLog!.Action.Should().Be("Delete");
        auditLog.EntityType.Should().Be("Project");
        auditLog.EntityId.Should().Be(projectId);
        auditLog.TenantId.Value.Should().Be(_tenantId);
    }

    [Fact]
    public async Task CreateEpic_Should_CreateAuditLog()
    {
        // Arrange: Create project first
        var projectResponse = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Project for Epic",
            Key = "PFE",
            Description = "Test project"
        });
        var project = await projectResponse.Content.ReadFromJsonAsync<ProjectDto>();
        var projectId = project!.Id;

        // Act: Create epic
        var epicResponse = await _client.PostAsJsonAsync("/api/v1/epics", new
        {
            ProjectId = projectId,
            Name = "Test Epic",
            Description = "Testing epic audit",
            CreatedBy = _userId
        });
        epicResponse.EnsureSuccessStatusCode();
        var epic = await epicResponse.Content.ReadFromJsonAsync<EpicDto>();
        var epicId = epic!.Id;

        // Assert: Check audit log
        using var scope = _factory.Services.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<PMDbContext>();

        var auditLog = await context.AuditLogs
            .IgnoreQueryFilters()
            .Where(a => a.EntityType == "Epic" && a.EntityId == epicId && a.Action == "Create")
            .FirstOrDefaultAsync();

        auditLog.Should().NotBeNull();
        auditLog!.Action.Should().Be("Create");
        auditLog.EntityType.Should().Be("Epic");
        auditLog.TenantId.Value.Should().Be(_tenantId);
    }

    [Fact]
    public async Task CreateStory_Should_CreateAuditLog()
    {
        // Arrange: Create project and epic
        var projectResponse = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Project for Story",
            Key = "PFS",
            Description = "Test"
        });
        var project = await projectResponse.Content.ReadFromJsonAsync<ProjectDto>();

        var epicResponse = await _client.PostAsJsonAsync("/api/v1/epics", new
        {
            ProjectId = project!.Id,
            Name = "Epic for Story",
            Description = "Test",
            CreatedBy = _userId
        });
        var epic = await epicResponse.Content.ReadFromJsonAsync<EpicDto>();

        // Act: Create story
        var storyResponse = await _client.PostAsJsonAsync("/api/v1/stories", new
        {
            EpicId = epic!.Id,
            Title = "Test Story",
            Description = "Testing story audit",
            Priority = "Medium",
            CreatedBy = _userId
        });
        storyResponse.EnsureSuccessStatusCode();
        var story = await storyResponse.Content.ReadFromJsonAsync<StoryDto>();
        var storyId = story!.Id;

        // Assert: Check audit log
        using var scope = _factory.Services.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<PMDbContext>();

        var auditLog = await context.AuditLogs
            .IgnoreQueryFilters()
            .Where(a => a.EntityType == "Story" && a.EntityId == storyId && a.Action == "Create")
            .FirstOrDefaultAsync();

        auditLog.Should().NotBeNull();
        auditLog!.EntityType.Should().Be("Story");
        auditLog.TenantId.Value.Should().Be(_tenantId);
    }

    [Fact]
    public async Task CreateTask_Should_CreateAuditLog()
    {
        // Arrange: Create project, epic, and story
        var projectResponse = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Project for Task",
            Key = "PFT",
            Description = "Test"
        });
        var project = await projectResponse.Content.ReadFromJsonAsync<ProjectDto>();

        var epicResponse = await _client.PostAsJsonAsync("/api/v1/epics", new
        {
            ProjectId = project!.Id,
            Name = "Epic for Task",
            Description = "Test",
            CreatedBy = _userId
        });
        var epic = await epicResponse.Content.ReadFromJsonAsync<EpicDto>();

        var storyResponse = await _client.PostAsJsonAsync("/api/v1/stories", new
        {
            EpicId = epic!.Id,
            Title = "Story for Task",
            Description = "Test",
            Priority = "Medium",
            CreatedBy = _userId
        });
        var story = await storyResponse.Content.ReadFromJsonAsync<StoryDto>();

        // Act: Create task
        var taskResponse = await _client.PostAsJsonAsync("/api/v1/tasks", new
        {
            StoryId = story!.Id,
            Title = "Test Task",
            Description = "Testing task audit",
            Priority = "High",
            CreatedBy = _userId
        });
        taskResponse.EnsureSuccessStatusCode();
        var task = await taskResponse.Content.ReadFromJsonAsync<TaskDto>();
        var taskId = task!.Id;

        // Assert: Check audit log
        using var scope = _factory.Services.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<PMDbContext>();

        var auditLog = await context.AuditLogs
            .IgnoreQueryFilters()
            .Where(a => a.EntityType == "WorkTask" && a.EntityId == taskId && a.Action == "Create")
            .FirstOrDefaultAsync();

        auditLog.Should().NotBeNull();
        auditLog!.EntityType.Should().Be("WorkTask");
        auditLog.TenantId.Value.Should().Be(_tenantId);
    }

    [Fact]
    public async Task AuditLog_Should_NotAuditItself()
    {
        // Arrange & Act: Create a project (which triggers audit log creation)
        var createResponse = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Test Recursion",
            Key = "TREC",
            Description = "Test"
        });
        createResponse.EnsureSuccessStatusCode();

        // Assert: Verify no AuditLog entity has been audited
        using var scope = _factory.Services.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<PMDbContext>();

        var selfAudit = await context.AuditLogs
            .IgnoreQueryFilters()
            .Where(a => a.EntityType == "AuditLog")
            .FirstOrDefaultAsync();

        selfAudit.Should().BeNull("AuditLog should not audit itself to prevent recursion");
    }

    [Fact]
    public async Task MultipleOperations_Should_CreateMultipleAuditLogs()
    {
        // Act: Create, update, and delete a project
        var createResponse = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Multi-Op Project",
            Key = "MOP",
            Description = "Test"
        });
        var project = await createResponse.Content.ReadFromJsonAsync<ProjectDto>();
        var projectId = project!.Id;

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

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

        // Assert: Verify all 3 audit logs exist
        using var scope = _factory.Services.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<PMDbContext>();

        var auditLogs = await context.AuditLogs
            .IgnoreQueryFilters()
            .Where(a => a.EntityType == "Project" && a.EntityId == projectId)
            .OrderBy(a => a.Timestamp)
            .ToListAsync();

        auditLogs.Should().HaveCount(3);
        auditLogs[0].Action.Should().Be("Create");
        auditLogs[1].Action.Should().Be("Update");
        auditLogs[2].Action.Should().Be("Delete");
    }

    [Fact]
    public async Task AuditLog_Should_IsolateTenants()
    {
        // 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 response1 = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Tenant 1 Project",
            Key = "T1P",
            Description = "Test"
        });
        var project1 = await response1.Content.ReadFromJsonAsync<ProjectDto>();
        var project1Id = project1!.Id;

        // Arrange: Tenant 2 creates a project
        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 response2 = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = "Tenant 2 Project",
            Key = "T2P",
            Description = "Test"
        });

        // Assert: Verify audit logs are isolated by tenant
        using var scope = _factory.Services.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<PMDbContext>();

        // Bypass query filter to see all audit logs
        var allAuditLogs = await context.AuditLogs
            .IgnoreQueryFilters()
            .Where(a => a.EntityType == "Project")
            .ToListAsync();

        var tenant1Logs = allAuditLogs.Where(a => a.TenantId.Value == tenant1Id).ToList();
        var tenant2Logs = allAuditLogs.Where(a => a.TenantId.Value == tenant2Id).ToList();

        tenant1Logs.Should().HaveCountGreaterOrEqualTo(1);
        tenant2Logs.Should().HaveCountGreaterOrEqualTo(1);
        tenant1Logs.All(a => a.TenantId.Value == tenant1Id).Should().BeTrue();
        tenant2Logs.All(a => a.TenantId.Value == tenant2Id).Should().BeTrue();
    }
}
