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.Shifts;

public class ShiftCrudTests : IntegrationTestBase
{
    public ShiftCrudTests(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.ShiftSignups.RemoveRange(context.ShiftSignups);
        context.Shifts.RemoveRange(context.Shifts);
        context.Members.RemoveRange(context.Members);
        context.Clubs.RemoveRange(context.Clubs);
        await context.SaveChangesAsync();
    }

    private async Task<(Guid clubId, Guid memberId, string externalUserId)> SeedMemberAsync(
        string tenantId,
        string email,
        string? externalUserId = null,
        ClubRole role = ClubRole.Member)
    {
        externalUserId ??= Guid.NewGuid().ToString();
        var clubId = Guid.NewGuid();
        var memberId = Guid.NewGuid();
        var now = DateTimeOffset.UtcNow;

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

        var existingClub = await context.Clubs.FirstOrDefaultAsync(c => c.TenantId == tenantId);
        if (existingClub != null)
        {
            clubId = existingClub.Id;
        }
        else
        {
            context.Clubs.Add(new Club
            {
                Id = clubId,
                TenantId = tenantId,
                Name = "Test Club",
                SportType = SportType.Tennis,
                CreatedAt = now,
                UpdatedAt = now
            });
        }

        context.Members.Add(new Member
        {
            Id = memberId,
            TenantId = tenantId,
            ExternalUserId = externalUserId,
            DisplayName = email.Split('@')[0],
            Email = email,
            Role = role,
            ClubId = clubId,
            CreatedAt = now,
            UpdatedAt = now
        });

        await context.SaveChangesAsync();
        return (clubId, memberId, externalUserId);
    }

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

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

        var request = new
        {
            Title = "Morning Shift",
            Description = "Morning work shift",
            Location = "Main Office",
            StartTime = DateTimeOffset.UtcNow.AddDays(1),
            EndTime = DateTimeOffset.UtcNow.AddDays(1).AddHours(4),
            Capacity = 5,
            ClubId = clubId
        };

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

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

        var result = await response.Content.ReadFromJsonAsync<ShiftDetailResponse>();
        Assert.NotNull(result);
        Assert.Equal("Morning Shift", result.Title);
        Assert.Equal(5, result.Capacity);
        Assert.NotEqual(Guid.Empty, result.Id);
    }

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

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

        var request = new
        {
            Title = "Morning Shift",
            StartTime = DateTimeOffset.UtcNow.AddDays(1),
            EndTime = DateTimeOffset.UtcNow.AddDays(1).AddHours(4),
            ClubId = clubId
        };

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

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

    [Fact]
    public async Task ListShifts_WithDateFilter_ReturnsFilteredShifts()
    {
        var clubId = Guid.NewGuid();
        var createdBy = Guid.NewGuid();
        var now = DateTimeOffset.UtcNow;

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

            context.Shifts.Add(new Shift
            {
                Id = Guid.NewGuid(),
                TenantId = "tenant1",
                Title = "Shift 1",
                StartTime = now.AddDays(2),
                EndTime = now.AddDays(2).AddHours(4),
                Capacity = 5,
                ClubId = clubId,
                CreatedById = createdBy,
                CreatedAt = now,
                UpdatedAt = now
            });

            context.Shifts.Add(new Shift
            {
                Id = Guid.NewGuid(),
                TenantId = "tenant1",
                Title = "Shift 2",
                StartTime = now.AddDays(10),
                EndTime = now.AddDays(10).AddHours(4),
                Capacity = 5,
                ClubId = clubId,
                CreatedById = createdBy,
                CreatedAt = now,
                UpdatedAt = now
            });

            await context.SaveChangesAsync();
        }

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

        var from = Uri.EscapeDataString(now.AddDays(1).ToString("o"));
        var to = Uri.EscapeDataString(now.AddDays(5).ToString("o"));

        var response = await Client.GetAsync($"/api/shifts?from={from}&to={to}");

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

        var result = await response.Content.ReadFromJsonAsync<ShiftListResponse>();
        Assert.NotNull(result);
        Assert.Single(result.Items);
        Assert.Equal("Shift 1", result.Items[0].Title);
    }

    [Fact]
    public async Task GetShift_ById_ReturnsShiftWithSignupList()
    {
        // Arrange
        var shiftId = Guid.NewGuid();
        var (clubId, memberId, externalUserId) = await SeedMemberAsync("tenant1", "member@test.com");
        var createdBy = Guid.NewGuid();
        var now = DateTimeOffset.UtcNow;

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

            context.Shifts.Add(new Shift
            {
                Id = shiftId,
                TenantId = "tenant1",
                Title = "Test Shift",
                Description = "Test Description",
                Location = "Test Location",
                StartTime = now.AddDays(1),
                EndTime = now.AddDays(1).AddHours(4),
                Capacity = 5,
                ClubId = clubId,
                CreatedById = createdBy,
                CreatedAt = now,
                UpdatedAt = now
            });

            context.ShiftSignups.Add(new ShiftSignup
            {
                Id = Guid.NewGuid(),
                TenantId = "tenant1",
                ShiftId = shiftId,
                MemberId = memberId,
                SignedUpAt = now
            });

            await context.SaveChangesAsync();
        }

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

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

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

        var result = await response.Content.ReadFromJsonAsync<ShiftDetailResponse>();
        Assert.NotNull(result);
        Assert.Equal(shiftId, result.Id);
        Assert.Equal("Test Shift", result.Title);
        Assert.Single(result.Signups);
        Assert.Equal(memberId, result.Signups[0].MemberId);
    }

    [Fact]
    public async Task UpdateShift_AsManager_UpdatesShift()
    {
        // Arrange
        var shiftId = Guid.NewGuid();
        var clubId = Guid.NewGuid();
        var createdBy = Guid.NewGuid();
        var now = DateTimeOffset.UtcNow;

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

            context.Shifts.Add(new Shift
            {
                Id = shiftId,
                TenantId = "tenant1",
                Title = "Original Title",
                StartTime = now.AddDays(1),
                EndTime = now.AddDays(1).AddHours(4),
                Capacity = 5,
                ClubId = clubId,
                CreatedById = createdBy,
                CreatedAt = now,
                UpdatedAt = now
            });

            await context.SaveChangesAsync();
        }

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

        var updateRequest = new
        {
            Title = "Updated Title",
            Capacity = 10
        };

        // Act
        var response = await Client.PutAsync($"/api/shifts/{shiftId}", JsonContent.Create(updateRequest));

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

        var result = await response.Content.ReadFromJsonAsync<ShiftDetailResponse>();
        Assert.NotNull(result);
        Assert.Equal("Updated Title", result.Title);
        Assert.Equal(10, result.Capacity);
    }

    [Fact]
    public async Task DeleteShift_AsManager_DeletesShift()
    {
        // Arrange
        var shiftId = Guid.NewGuid();
        var clubId = Guid.NewGuid();
        var createdBy = Guid.NewGuid();
        var now = DateTimeOffset.UtcNow;

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

            context.Shifts.Add(new Shift
            {
                Id = shiftId,
                TenantId = "tenant1",
                Title = "Test Shift",
                StartTime = now.AddDays(1),
                EndTime = now.AddDays(1).AddHours(4),
                Capacity = 5,
                ClubId = clubId,
                CreatedById = createdBy,
                CreatedAt = now,
                UpdatedAt = now
            });

            await context.SaveChangesAsync();
        }

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

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

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

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

    [Fact]
    public async Task SignUpForShift_WithCapacity_ReturnsOk()
    {
        // Arrange
        var (clubId, memberId, externalUserId) = await SeedMemberAsync("tenant1", "member@test.com");
        var shiftId = Guid.NewGuid();
        var createdBy = Guid.NewGuid();
        var now = DateTimeOffset.UtcNow;

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

            context.Shifts.Add(new Shift
            {
                Id = shiftId,
                TenantId = "tenant1",
                Title = "Test Shift",
                StartTime = now.AddDays(1),
                EndTime = now.AddDays(1).AddHours(4),
                Capacity = 5,
                ClubId = clubId,
                CreatedById = createdBy,
                CreatedAt = now,
                UpdatedAt = now
            });

            await context.SaveChangesAsync();
        }

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

        // Act
        var response = await Client.PostAsync($"/api/shifts/{shiftId}/signup", null);

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

        // Verify signup was created
        using (var scope = Factory.Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            var signups = await context.ShiftSignups.Where(ss => ss.ShiftId == shiftId).ToListAsync();
            Assert.Single(signups);
            Assert.Equal(memberId, signups[0].MemberId);
        }
    }

    [Fact]
    public async Task SignUpForShift_WhenFull_ReturnsConflict()
    {
        // Arrange
        var (clubId, _, externalUserId) = await SeedMemberAsync("tenant1", "member@test.com");
        var shiftId = Guid.NewGuid();
        var createdBy = Guid.NewGuid();
        var now = DateTimeOffset.UtcNow;

        // Seed a different member to fill the single slot
        var (_, fillerMemberId, _) = await SeedMemberAsync("tenant1", "filler@test.com");

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

            context.Shifts.Add(new Shift
            {
                Id = shiftId,
                TenantId = "tenant1",
                Title = "Test Shift",
                StartTime = now.AddDays(1),
                EndTime = now.AddDays(1).AddHours(4),
                Capacity = 1,
                ClubId = clubId,
                CreatedById = createdBy,
                CreatedAt = now,
                UpdatedAt = now
            });

            // Add one signup to fill capacity
            context.ShiftSignups.Add(new ShiftSignup
            {
                Id = Guid.NewGuid(),
                TenantId = "tenant1",
                ShiftId = shiftId,
                MemberId = fillerMemberId,
                SignedUpAt = now
            });

            await context.SaveChangesAsync();
        }

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

        // Act
        var response = await Client.PostAsync($"/api/shifts/{shiftId}/signup", null);

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

    [Fact]
    public async Task SignUpForShift_ForPastShift_ReturnsUnprocessableEntity()
    {
        // Arrange
        var (clubId, _, externalUserId) = await SeedMemberAsync("tenant1", "member@test.com");
        var shiftId = Guid.NewGuid();
        var createdBy = Guid.NewGuid();
        var now = DateTimeOffset.UtcNow;

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

            context.Shifts.Add(new Shift
            {
                Id = shiftId,
                TenantId = "tenant1",
                Title = "Past Shift",
                StartTime = now.AddHours(-2),
                EndTime = now.AddHours(-1),
                Capacity = 5,
                ClubId = clubId,
                CreatedById = createdBy,
                CreatedAt = now.AddDays(-1),
                UpdatedAt = now.AddDays(-1)
            });

            await context.SaveChangesAsync();
        }

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

        // Act
        var response = await Client.PostAsync($"/api/shifts/{shiftId}/signup", null);

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

    [Fact]
    public async Task SignUpForShift_Duplicate_ReturnsConflict()
    {
        // Arrange
        var (clubId, memberId, externalUserId) = await SeedMemberAsync("tenant1", "member@test.com");
        var shiftId = Guid.NewGuid();
        var createdBy = Guid.NewGuid();
        var now = DateTimeOffset.UtcNow;

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

            context.Shifts.Add(new Shift
            {
                Id = shiftId,
                TenantId = "tenant1",
                Title = "Test Shift",
                StartTime = now.AddDays(1),
                EndTime = now.AddDays(1).AddHours(4),
                Capacity = 5,
                ClubId = clubId,
                CreatedById = createdBy,
                CreatedAt = now,
                UpdatedAt = now
            });

            context.ShiftSignups.Add(new ShiftSignup
            {
                Id = Guid.NewGuid(),
                TenantId = "tenant1",
                ShiftId = shiftId,
                MemberId = memberId,
                SignedUpAt = now
            });

            await context.SaveChangesAsync();
        }

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

        // Act
        var response = await Client.PostAsync($"/api/shifts/{shiftId}/signup", null);

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

    [Fact]
    public async Task CancelSignup_BeforeShift_ReturnsOk()
    {
        // Arrange
        var (clubId, memberId, externalUserId) = await SeedMemberAsync("tenant1", "member@test.com");
        var shiftId = Guid.NewGuid();
        var createdBy = Guid.NewGuid();
        var now = DateTimeOffset.UtcNow;

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

            context.Shifts.Add(new Shift
            {
                Id = shiftId,
                TenantId = "tenant1",
                Title = "Test Shift",
                StartTime = now.AddDays(1),
                EndTime = now.AddDays(1).AddHours(4),
                Capacity = 5,
                ClubId = clubId,
                CreatedById = createdBy,
                CreatedAt = now,
                UpdatedAt = now
            });

            context.ShiftSignups.Add(new ShiftSignup
            {
                Id = Guid.NewGuid(),
                TenantId = "tenant1",
                ShiftId = shiftId,
                MemberId = memberId,
                SignedUpAt = now
            });

            await context.SaveChangesAsync();
        }

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

        // Act
        var response = await Client.DeleteAsync($"/api/shifts/{shiftId}/signup");

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

        using (var scope = Factory.Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            var signups = await context.ShiftSignups.Where(ss => ss.ShiftId == shiftId && ss.MemberId == memberId).ToListAsync();
            Assert.Empty(signups);
        }
    }

    [Fact]
    public async Task SignUpForShift_ConcurrentLastSlot_HandlesRaceCondition()
    {
        // Arrange
        var (clubId, fillerMemberId, _) = await SeedMemberAsync("tenant1", "filler@test.com");
        var (_, _, externalUserId1) = await SeedMemberAsync("tenant1", "member1@test.com");
        var (_, _, externalUserId2) = await SeedMemberAsync("tenant1", "member2@test.com");

        var shiftId = Guid.NewGuid();
        var createdBy = Guid.NewGuid();
        var now = DateTimeOffset.UtcNow;

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

            context.Shifts.Add(new Shift
            {
                Id = shiftId,
                TenantId = "tenant1",
                Title = "Test Shift",
                StartTime = now.AddDays(1),
                EndTime = now.AddDays(1).AddHours(4),
                Capacity = 2,
                ClubId = clubId,
                CreatedById = createdBy,
                CreatedAt = now,
                UpdatedAt = now
            });

            context.ShiftSignups.Add(new ShiftSignup
            {
                Id = Guid.NewGuid(),
                TenantId = "tenant1",
                ShiftId = shiftId,
                MemberId = fillerMemberId,
                SignedUpAt = now
            });

            await context.SaveChangesAsync();
        }

        SetTenant("tenant1");

        // Act
        AuthenticateAs("member1@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" }, externalUserId1);
        var response1Task = Client.PostAsync($"/api/shifts/{shiftId}/signup", null);

        AuthenticateAs("member2@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" }, externalUserId2);
        var response2Task = Client.PostAsync($"/api/shifts/{shiftId}/signup", null);

        var responses = await Task.WhenAll(response1Task, response2Task);

        // Assert
        var statuses = responses.Select(r => r.StatusCode).OrderBy(s => s).ToList();
        Assert.Contains(HttpStatusCode.OK, statuses);
        Assert.Contains(HttpStatusCode.Conflict, statuses);

        using (var scope = Factory.Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            var signupCount = await context.ShiftSignups.CountAsync(ss => ss.ShiftId == shiftId);
            Assert.Equal(2, signupCount);
        }
    }
}

// Response DTOs for test assertions
public record ShiftListResponse(List<ShiftListItemResponse> Items, int Total, int Page, int PageSize);
public record ShiftListItemResponse(Guid Id, string Title, DateTimeOffset StartTime, DateTimeOffset EndTime, int Capacity, int CurrentSignups, bool IsSignedUp);
public record ShiftDetailResponse(Guid Id, string Title, string? Description, string? Location, DateTimeOffset StartTime, DateTimeOffset EndTime, int Capacity, List<ShiftSignupResponse> Signups, Guid ClubId, Guid CreatedById, DateTimeOffset CreatedAt, DateTimeOffset UpdatedAt);
public record ShiftSignupResponse(Guid Id, Guid MemberId, DateTimeOffset SignedUpAt);
