using CartWise.Application.Services;
using CartWise.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace CartWise.Application.Tests;

public class ShoppingListServiceTests : IDisposable
{
    private readonly CartWiseDbContext _db;
    private readonly ShoppingListService _sut;

    public ShoppingListServiceTests()
    {
        var options = new DbContextOptionsBuilder<CartWiseDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;

        _db = new CartWiseDbContext(options);
        _sut = new ShoppingListService(_db, TimeProvider.System);
    }

    public void Dispose()
    {
        _db.Dispose();
        GC.SuppressFinalize(this);
    }

    [Fact]
    public async Task CreateListAsync_CreatesActiveList()
    {
        var householdId = Guid.NewGuid();

        var result = await _sut.CreateListAsync(householdId, "Groceries", "user-1");

        Assert.True(result.IsSuccess);
        Assert.Equal("Groceries", result.Value!.Name);
        Assert.Equal(householdId, result.Value.HouseholdId);
    }

    [Fact]
    public async Task CreateListAsync_FailsWhenHouseholdAlreadyHasAnActiveList()
    {
        var householdId = Guid.NewGuid();
        await _sut.CreateListAsync(householdId, "Groceries", "user-1");

        var result = await _sut.CreateListAsync(householdId, "Second List", "user-1");

        Assert.False(result.IsSuccess);
        Assert.NotNull(result.Error);
    }

    [Fact]
    public async Task CreateListAsync_FailsWhenNameIsMissing()
    {
        var result = await _sut.CreateListAsync(Guid.NewGuid(), "   ", "user-1");

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task GetActiveListAsync_ReturnsNullWhenHouseholdHasNoList()
    {
        var list = await _sut.GetActiveListAsync(Guid.NewGuid());

        Assert.Null(list);
    }

    [Fact]
    public async Task GetActiveListAsync_ReturnsTheActiveList()
    {
        var householdId = Guid.NewGuid();
        var created = await _sut.CreateListAsync(householdId, "Groceries", "user-1");

        var list = await _sut.GetActiveListAsync(householdId);

        Assert.NotNull(list);
        Assert.Equal(created.Value!.ShoppingListId, list!.ShoppingListId);
    }

    [Fact]
    public async Task GetActiveListAsync_DoesNotReturnAnotherHouseholdsList()
    {
        var householdA = Guid.NewGuid();
        var householdB = Guid.NewGuid();
        await _sut.CreateListAsync(householdA, "Household A's List", "user-1");

        var list = await _sut.GetActiveListAsync(householdB);

        Assert.Null(list);
    }

    [Fact]
    public async Task AddItemAsync_AddsItemAndPersistsIt()
    {
        var created = await _sut.CreateListAsync(Guid.NewGuid(), "Groceries", "user-1");

        var result = await _sut.AddItemAsync(created.Value!.ShoppingListId, "Milk", 2m, "gal", "user-1");

        Assert.True(result.IsSuccess);
        Assert.Equal("Milk", result.Value!.DisplayName);

        var reloaded = await _sut.GetActiveListAsync(created.Value.HouseholdId);
        Assert.Single(reloaded!.Items);
        Assert.Equal("Milk", reloaded.Items.Single().DisplayName);
    }

    [Fact]
    public async Task AddItemAsync_FailsWhenListDoesNotExist()
    {
        var result = await _sut.AddItemAsync(Guid.NewGuid(), "Milk", null, null, "user-1");

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task AddItemAsync_FailsWhenDisplayNameIsMissing()
    {
        var created = await _sut.CreateListAsync(Guid.NewGuid(), "Groceries", "user-1");

        var result = await _sut.AddItemAsync(created.Value!.ShoppingListId, "   ", null, null, "user-1");

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task TogglePurchasedAsync_MarksItemPurchased()
    {
        var (householdId, item) = await CreateItemAsync();

        var result = await _sut.TogglePurchasedAsync(householdId, item.ShoppingListItemId, item.RowVersion);

        Assert.True(result.IsSuccess);
        Assert.Equal(Domain.Enums.ShoppingListItemStatus.Purchased, result.Value!.Status);
    }

    [Fact]
    public async Task TogglePurchasedAsync_FailsForItemInAnotherHousehold()
    {
        var (_, item) = await CreateItemAsync();

        var result = await _sut.TogglePurchasedAsync(Guid.NewGuid(), item.ShoppingListItemId, item.RowVersion);

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task TogglePurchasedAsync_FailsOnConcurrencyConflict()
    {
        var (householdId, item) = await CreateItemAsync();
        var staleRowVersion = Guid.NewGuid();

        var result = await _sut.TogglePurchasedAsync(householdId, item.ShoppingListItemId, staleRowVersion);

        Assert.False(result.IsSuccess);
        Assert.NotNull(result.Error);
    }

    [Fact]
    public async Task ToggleSkippedAsync_MarksItemSkipped()
    {
        var (householdId, item) = await CreateItemAsync();

        var result = await _sut.ToggleSkippedAsync(householdId, item.ShoppingListItemId, item.RowVersion);

        Assert.True(result.IsSuccess);
        Assert.Equal(Domain.Enums.ShoppingListItemStatus.Skipped, result.Value!.Status);
    }

    [Fact]
    public async Task ToggleSkippedAsync_UnskipsOnSecondCall()
    {
        var (householdId, item) = await CreateItemAsync();
        var firstResult = await _sut.ToggleSkippedAsync(householdId, item.ShoppingListItemId, item.RowVersion);

        var secondResult = await _sut.ToggleSkippedAsync(householdId, item.ShoppingListItemId, firstResult.Value!.RowVersion);

        Assert.True(secondResult.IsSuccess);
        Assert.Equal(Domain.Enums.ShoppingListItemStatus.Needed, secondResult.Value!.Status);
    }

    [Fact]
    public async Task DeleteItemAsync_RemovesItemFromList()
    {
        var (householdId, item) = await CreateItemAsync();

        var result = await _sut.DeleteItemAsync(householdId, item.ShoppingListItemId, item.RowVersion);

        Assert.True(result.IsSuccess);
        var list = await _sut.GetActiveListAsync(householdId);
        Assert.Empty(list!.Items);
    }

    [Fact]
    public async Task DeleteItemAsync_FailsForItemInAnotherHousehold()
    {
        var (_, item) = await CreateItemAsync();

        var result = await _sut.DeleteItemAsync(Guid.NewGuid(), item.ShoppingListItemId, item.RowVersion);

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task DeleteItemAsync_FailsOnConcurrencyConflict()
    {
        var (householdId, item) = await CreateItemAsync();
        var staleRowVersion = Guid.NewGuid();

        var result = await _sut.DeleteItemAsync(householdId, item.ShoppingListItemId, staleRowVersion);

        Assert.False(result.IsSuccess);
        var list = await _sut.GetActiveListAsync(householdId);
        Assert.Single(list!.Items);
    }

    private async Task<(Guid HouseholdId, Domain.Entities.ShoppingListItem Item)> CreateItemAsync()
    {
        var householdId = Guid.NewGuid();
        var list = await _sut.CreateListAsync(householdId, "Groceries", "user-1");
        var item = await _sut.AddItemAsync(list.Value!.ShoppingListId, "Milk", null, null, "user-1");

        return (householdId, item.Value!);
    }
}
