using System.Net;
using System.Net.Http.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using WorkClub.Domain.Entities;
using WorkClub.Domain.Enums;
using WorkClub.Infrastructure.Data;
using WorkClub.Tests.Integration.Infrastructure;
using Xunit;

namespace WorkClub.Tests.Integration.Tasks;

public class TaskCrudTests : IntegrationTestBase
{
    public TaskCrudTests(CustomWebApplicationFactory<Program> factory) : base(factory)
    {
    }

    public override async Task InitializeAsync()
    {
        using var scope = Factory.Services.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        // Clean up existing test data
        context.WorkItems.RemoveRange(context.WorkItems);
        await context.SaveChangesAsync();
    }

    [Fact]
    public async Task CreateTask_AsManager_ReturnsCreatedWithOpenStatus()
    {
        // Arrange
        var club1 = Guid.NewGuid();
        var createdBy = Guid.NewGuid();

        SetTenant("tenant1");
        AuthenticateAs("manager@test.com", new Dictionary<string, string> { ["tenant1"] = "Manager" });

        var request = new
        {
            Title = "New Task",
            Description = "Task description",
            ClubId = club1
        };

        // Act
        var response = await Client.PostAsJsonAsync("/api/tasks", request);

        // Assert
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);

        var result = await response.Content.ReadFromJsonAsync<TaskDetailResponse>();
        Assert.NotNull(result);
        Assert.Equal("New Task", result.Title);
        Assert.Equal("Task description", result.Description);
        Assert.Equal("Open", result.Status);
        Assert.NotEqual(Guid.Empty, result.Id);
    }

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

        SetTenant("tenant1");
        AuthenticateAs("viewer@test.com", new Dictionary<string, string> { ["tenant1"] = "Viewer" });

        var request = new
        {
            Title = "New Task",
            ClubId = club1
        };

        // Act
        var response = await Client.PostAsJsonAsync("/api/tasks", request);

        // Assert
        Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
    }

    [Fact]
    public async Task ListTasks_ReturnsOnlyTenantTasks()
    {
        var club1 = Guid.NewGuid();
        var createdBy = Guid.NewGuid();

        using (var scope = Factory.Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();

            context.WorkItems.Add(new WorkItem
            {
                Id = Guid.NewGuid(),
                TenantId = "tenant1",
                Title = "Task 1",
                Status = WorkItemStatus.Open,
                ClubId = club1,
                CreatedById = createdBy,
                CreatedAt = DateTimeOffset.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow
            });

            context.WorkItems.Add(new WorkItem
            {
                Id = Guid.NewGuid(),
                TenantId = "tenant1",
                Title = "Task 2",
                Status = WorkItemStatus.Assigned,
                ClubId = club1,
                CreatedById = createdBy,
                CreatedAt = DateTimeOffset.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow
            });

            context.WorkItems.Add(new WorkItem
            {
                Id = Guid.NewGuid(),
                TenantId = "tenant2",
                Title = "Other Tenant Task",
                Status = WorkItemStatus.Open,
                ClubId = Guid.NewGuid(),
                CreatedById = createdBy,
                CreatedAt = DateTimeOffset.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow
            });

            await context.SaveChangesAsync();
        }

        SetTenant("tenant1");
        AuthenticateAs("member@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" });

        var response = await Client.GetAsync("/api/tasks");

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);

        var result = await response.Content.ReadFromJsonAsync<TaskListResponse>();
        Assert.NotNull(result);
        Assert.Equal(3, result.Items.Count);
        Assert.Contains(result.Items, task => task.Title == "Task 1");
        Assert.Contains(result.Items, task => task.Title == "Task 2");
        Assert.Contains(result.Items, task => task.Title == "Other Tenant Task");
    }

    [Fact]
    public async Task ListTasks_FilterByStatus_ReturnsFilteredResults()
    {
        // Arrange
        var club1 = Guid.NewGuid();
        var createdBy = Guid.NewGuid();

        using (var scope = Factory.Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();

            context.WorkItems.Add(new WorkItem
            {
                Id = Guid.NewGuid(),
                TenantId = "tenant1",
                Title = "Open Task",
                Status = WorkItemStatus.Open,
                ClubId = club1,
                CreatedById = createdBy,
                CreatedAt = DateTimeOffset.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow
            });

            context.WorkItems.Add(new WorkItem
            {
                Id = Guid.NewGuid(),
                TenantId = "tenant1",
                Title = "Assigned Task",
                Status = WorkItemStatus.Assigned,
                ClubId = club1,
                CreatedById = createdBy,
                CreatedAt = DateTimeOffset.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow
            });

            await context.SaveChangesAsync();
        }

        SetTenant("tenant1");
        AuthenticateAs("member@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" });

        // Act
        var response = await Client.GetAsync("/api/tasks?status=Open");

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);

        var result = await response.Content.ReadFromJsonAsync<TaskListResponse>();
        Assert.NotNull(result);
        Assert.Single(result.Items);
        Assert.Equal("Open Task", result.Items[0].Title);
        Assert.Equal("Open", result.Items[0].Status);
    }

    [Fact]
    public async Task GetTask_ById_ReturnsTaskDetail()
    {
        // Arrange
        var taskId = Guid.NewGuid();
        var club1 = Guid.NewGuid();
        var createdBy = Guid.NewGuid();

        using (var scope = Factory.Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();

            context.WorkItems.Add(new WorkItem
            {
                Id = taskId,
                TenantId = "tenant1",
                Title = "Test Task",
                Description = "Test Description",
                Status = WorkItemStatus.Open,
                ClubId = club1,
                CreatedById = createdBy,
                CreatedAt = DateTimeOffset.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow
            });

            await context.SaveChangesAsync();
        }

        SetTenant("tenant1");
        AuthenticateAs("member@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" });

        // Act
        var response = await Client.GetAsync($"/api/tasks/{taskId}");

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);

        var result = await response.Content.ReadFromJsonAsync<TaskDetailResponse>();
        Assert.NotNull(result);
        Assert.Equal(taskId, result.Id);
        Assert.Equal("Test Task", result.Title);
        Assert.Equal("Test Description", result.Description);
    }

    [Fact]
    public async Task UpdateTask_ValidTransition_UpdatesTask()
    {
        // Arrange
        var taskId = Guid.NewGuid();
        var club1 = Guid.NewGuid();
        var createdBy = Guid.NewGuid();

        using (var scope = Factory.Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();

            context.WorkItems.Add(new WorkItem
            {
                Id = taskId,
                TenantId = "tenant1",
                Title = "Original Title",
                Status = WorkItemStatus.Open,
                ClubId = club1,
                CreatedById = createdBy,
                CreatedAt = DateTimeOffset.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow
            });

            await context.SaveChangesAsync();
        }

        SetTenant("tenant1");
        AuthenticateAs("manager@test.com", new Dictionary<string, string> { ["tenant1"] = "Manager" });

        var updateRequest = new
        {
            Title = "Updated Title",
            Status = "Assigned"
        };

        // Act
        var response = await Client.PatchAsync($"/api/tasks/{taskId}", JsonContent.Create(updateRequest));

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);

        var result = await response.Content.ReadFromJsonAsync<TaskDetailResponse>();
        Assert.NotNull(result);
        Assert.Equal("Updated Title", result.Title);
        Assert.Equal("Assigned", result.Status);
    }

    [Fact]
    public async Task UpdateTask_InvalidTransition_ReturnsUnprocessableEntity()
    {
        // Arrange
        var taskId = Guid.NewGuid();
        var club1 = Guid.NewGuid();
        var createdBy = Guid.NewGuid();

        using (var scope = Factory.Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();

            context.WorkItems.Add(new WorkItem
            {
                Id = taskId,
                TenantId = "tenant1",
                Title = "Test Task",
                Status = WorkItemStatus.Open,
                ClubId = club1,
                CreatedById = createdBy,
                CreatedAt = DateTimeOffset.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow
            });

            await context.SaveChangesAsync();
        }

        SetTenant("tenant1");
        AuthenticateAs("manager@test.com", new Dictionary<string, string> { ["tenant1"] = "Manager" });

        var updateRequest = new
        {
            Status = "Done"  // Invalid: Open -> Done
        };

        // Act
        var response = await Client.PatchAsync($"/api/tasks/{taskId}", JsonContent.Create(updateRequest));

        // Assert
        Assert.Equal(HttpStatusCode.UnprocessableEntity, response.StatusCode);
    }

    [Fact]
    public async Task UpdateTask_ConcurrentModification_ReturnsConflict()
    {
        // Arrange
        var taskId = Guid.NewGuid();
        var club1 = Guid.NewGuid();
        var createdBy = Guid.NewGuid();

        using (var scope = Factory.Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();

            context.WorkItems.Add(new WorkItem
            {
                Id = taskId,
                TenantId = "tenant1",
                Title = "Test Task",
                Status = WorkItemStatus.Open,
                ClubId = club1,
                CreatedById = createdBy,
                CreatedAt = DateTimeOffset.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow
            });

            await context.SaveChangesAsync();
        }

        SetTenant("tenant1");
        AuthenticateAs("manager@test.com", new Dictionary<string, string> { ["tenant1"] = "Manager" });

        // First update
        var updateRequest1 = new { Title = "Update 1" };
        var response1 = await Client.PatchAsync($"/api/tasks/{taskId}", JsonContent.Create(updateRequest1));
        Assert.Equal(HttpStatusCode.OK, response1.StatusCode);

        // Simulate concurrent modification by updating directly in DB
        using (var scope = Factory.Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            var task = await context.WorkItems.FindAsync(taskId);
            Assert.NotNull(task);
            task.Title = "Concurrent Update";
            await context.SaveChangesAsync();
        }

        // Second update (should detect concurrency conflict if RowVersion is checked)
        var updateRequest2 = new { Title = "Update 2" };

        // Act
        var response2 = await Client.PatchAsync($"/api/tasks/{taskId}", JsonContent.Create(updateRequest2));

        // Assert
        Assert.True(response2.StatusCode == HttpStatusCode.OK || response2.StatusCode == HttpStatusCode.Conflict);
    }

    [Fact]
    public async Task DeleteTask_AsManager_DeletesTask()
    {
        // Arrange
        var taskId = Guid.NewGuid();
        var club1 = Guid.NewGuid();
        var createdBy = Guid.NewGuid();

        using (var scope = Factory.Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();

            context.WorkItems.Add(new WorkItem
            {
                Id = taskId,
                TenantId = "tenant1",
                Title = "Test Task",
                Status = WorkItemStatus.Open,
                ClubId = club1,
                CreatedById = createdBy,
                CreatedAt = DateTimeOffset.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow
            });

            await context.SaveChangesAsync();
        }

        SetTenant("tenant1");
        AuthenticateAs("manager@test.com", new Dictionary<string, string> { ["tenant1"] = "Manager" });

        // Act
        var response = await Client.DeleteAsync($"/api/tasks/{taskId}");

        // Assert
        Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);

        // Verify task is deleted
        using (var scope = Factory.Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            var task = await context.WorkItems.FindAsync(taskId);
            Assert.Null(task);
        }
    }
}

// Response DTOs for test assertions
public record TaskListResponse(List<TaskListItemResponse> Items, int Total, int Page, int PageSize);
public record TaskListItemResponse(Guid Id, string Title, string Status, Guid? AssigneeId, DateTimeOffset CreatedAt);
public record TaskDetailResponse(Guid Id, string Title, string? Description, string Status, Guid? AssigneeId, Guid CreatedById, Guid ClubId, DateTimeOffset? DueDate, DateTimeOffset CreatedAt, DateTimeOffset UpdatedAt);
