using CartWise.Application.Interfaces;
using CartWise.Application.Results;
using CartWise.Domain.Entities;
using CartWise.Domain.Enums;
using Microsoft.EntityFrameworkCore;

namespace CartWise.Application.Services;

public class ShoppingListService : IShoppingListService
{
    private readonly IApplicationDbContext _db;
    private readonly TimeProvider _timeProvider;

    public ShoppingListService(IApplicationDbContext db, TimeProvider timeProvider)
    {
        _db = db;
        _timeProvider = timeProvider;
    }

    public async Task<ShoppingList?> GetActiveListAsync(Guid householdId, CancellationToken cancellationToken = default)
    {
        return await _db.ShoppingLists
            .Include(l => l.Items)
            .AsNoTracking()
            .Where(l => l.HouseholdId == householdId && l.Status == ShoppingListStatus.Active)
            .FirstOrDefaultAsync(cancellationToken);
    }

    public async Task<Result<ShoppingList>> CreateListAsync(Guid householdId, string name, string createdByUserId, CancellationToken cancellationToken = default)
    {
        var hasActiveList = await _db.ShoppingLists
            .AsNoTracking()
            .AnyAsync(l => l.HouseholdId == householdId && l.Status == ShoppingListStatus.Active, cancellationToken);

        if (hasActiveList)
        {
            return Result<ShoppingList>.Failure("This household already has an active shopping list.");
        }

        ShoppingList list;
        try
        {
            list = new ShoppingList(householdId, name, createdByUserId, _timeProvider.GetUtcNow().UtcDateTime);
        }
        catch (ArgumentException ex)
        {
            return Result<ShoppingList>.Failure(ex.Message);
        }

        _db.ShoppingLists.Add(list);
        await _db.SaveChangesAsync(cancellationToken);

        return Result<ShoppingList>.Success(list);
    }

    public async Task<Result<ShoppingListItem>> AddItemAsync(
        Guid shoppingListId,
        string displayName,
        decimal? quantity,
        string? unit,
        string addedByUserId,
        CancellationToken cancellationToken = default)
    {
        var list = await _db.ShoppingLists
            .Include(l => l.Items)
            .FirstOrDefaultAsync(l => l.ShoppingListId == shoppingListId, cancellationToken);

        if (list is null)
        {
            return Result<ShoppingListItem>.Failure("Shopping list not found.");
        }

        ShoppingListItem item;
        try
        {
            item = list.AddItem(displayName, quantity, unit, addedByUserId, _timeProvider.GetUtcNow().UtcDateTime);
        }
        catch (Exception ex) when (ex is ArgumentException or InvalidOperationException)
        {
            return Result<ShoppingListItem>.Failure(ex.Message);
        }

        // EF Core can't reliably infer Added state for a new child discovered only via a mutated
        // navigation collection on an already-tracked parent, since ShoppingListItemId is a
        // client-generated Guid rather than a store-generated key. Mark it explicitly.
        _db.ShoppingListItems.Add(item);
        await _db.SaveChangesAsync(cancellationToken);

        return Result<ShoppingListItem>.Success(item);
    }

    public async Task<Result<ShoppingListItem>> TogglePurchasedAsync(Guid householdId, Guid itemId, Guid expectedRowVersion, CancellationToken cancellationToken = default)
    {
        var item = await FindItemForHouseholdAsync(householdId, itemId, cancellationToken);
        if (item is null)
        {
            return Result<ShoppingListItem>.Failure("Item not found.");
        }

        _db.Entry(item).Property(i => i.RowVersion).OriginalValue = expectedRowVersion;
        item.TogglePurchased(_timeProvider.GetUtcNow().UtcDateTime);

        try
        {
            await _db.SaveChangesAsync(cancellationToken);
        }
        catch (DbUpdateConcurrencyException)
        {
            return Result<ShoppingListItem>.Failure("This item was changed by someone else. Please refresh and try again.");
        }

        return Result<ShoppingListItem>.Success(item);
    }

    public async Task<Result<ShoppingListItem>> ToggleSkippedAsync(Guid householdId, Guid itemId, Guid expectedRowVersion, CancellationToken cancellationToken = default)
    {
        var item = await FindItemForHouseholdAsync(householdId, itemId, cancellationToken);
        if (item is null)
        {
            return Result<ShoppingListItem>.Failure("Item not found.");
        }

        _db.Entry(item).Property(i => i.RowVersion).OriginalValue = expectedRowVersion;
        item.ToggleSkipped();

        try
        {
            await _db.SaveChangesAsync(cancellationToken);
        }
        catch (DbUpdateConcurrencyException)
        {
            return Result<ShoppingListItem>.Failure("This item was changed by someone else. Please refresh and try again.");
        }

        return Result<ShoppingListItem>.Success(item);
    }

    public async Task<Result> DeleteItemAsync(Guid householdId, Guid itemId, Guid expectedRowVersion, CancellationToken cancellationToken = default)
    {
        var item = await FindItemForHouseholdAsync(householdId, itemId, cancellationToken);
        if (item is null)
        {
            return Result.Failure("Item not found.");
        }

        _db.Entry(item).Property(i => i.RowVersion).OriginalValue = expectedRowVersion;
        _db.ShoppingListItems.Remove(item);

        try
        {
            await _db.SaveChangesAsync(cancellationToken);
        }
        catch (DbUpdateConcurrencyException)
        {
            return Result.Failure("This item was changed by someone else. Please refresh and try again.");
        }

        return Result.Success();
    }

    private Task<ShoppingListItem?> FindItemForHouseholdAsync(Guid householdId, Guid itemId, CancellationToken cancellationToken)
    {
        return (
            from item in _db.ShoppingListItems
            join list in _db.ShoppingLists on item.ShoppingListId equals list.ShoppingListId
            where item.ShoppingListItemId == itemId && list.HouseholdId == householdId
            select item
        ).FirstOrDefaultAsync(cancellationToken);
    }
}
