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

namespace CartWise.Application.Services;

public class PurchaseService : IPurchaseService
{
    private readonly IApplicationDbContext _db;
    private readonly TimeProvider _timeProvider;

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

    public async Task<Result<Purchase>> StartPurchaseAsync(
        Guid householdId,
        string purchasedByUserId,
        DateTime purchasedUtc,
        Guid? storeLocationId = null,
        CancellationToken cancellationToken = default)
    {
        Purchase purchase;
        try
        {
            purchase = new Purchase(householdId, purchasedByUserId, purchasedUtc, _timeProvider.GetUtcNow().UtcDateTime, storeLocationId: storeLocationId);
        }
        catch (ArgumentException ex)
        {
            return Result<Purchase>.Failure(ex.Message);
        }

        _db.Purchases.Add(purchase);
        await _db.SaveChangesAsync(cancellationToken);

        return Result<Purchase>.Success(purchase);
    }

    public async Task<Result<PurchaseItem>> AddPurchaseItemAsync(
        Guid householdId,
        Guid purchaseId,
        string description,
        decimal linePrice,
        decimal quantity = 1m,
        string? unit = null,
        Guid? productId = null,
        Guid? shoppingListItemId = null,
        CancellationToken cancellationToken = default)
    {
        var purchase = await FindPurchaseForHouseholdAsync(householdId, purchaseId, cancellationToken);
        if (purchase is null)
        {
            return Result<PurchaseItem>.Failure("Purchase not found.");
        }

        PurchaseItem item;
        try
        {
            item = purchase.AddItem(
                description,
                linePrice,
                _timeProvider.GetUtcNow().UtcDateTime,
                quantity: quantity,
                unit: unit,
                productId: productId,
                shoppingListItemId: shoppingListItemId);
        }
        catch (Exception ex) when (ex is ArgumentException or InvalidOperationException)
        {
            return Result<PurchaseItem>.Failure(ex.Message);
        }

        // Same EF Core tracking gotcha as ShoppingListService.AddItemAsync: a new child
        // discovered only via a mutated navigation collection on an already-tracked parent
        // needs to be explicitly marked, since PurchaseItemId is a client-generated Guid.
        _db.PurchaseItems.Add(item);
        await _db.SaveChangesAsync(cancellationToken);

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

    public async Task<Result<Purchase>> CompletePurchaseAsync(
        Guid householdId,
        Guid purchaseId,
        decimal? tax = null,
        CancellationToken cancellationToken = default)
    {
        var purchase = await FindPurchaseForHouseholdAsync(householdId, purchaseId, cancellationToken);
        if (purchase is null)
        {
            return Result<Purchase>.Failure("Purchase not found.");
        }

        try
        {
            purchase.Complete(tax);
        }
        catch (InvalidOperationException ex)
        {
            return Result<Purchase>.Failure(ex.Message);
        }

        await _db.SaveChangesAsync(cancellationToken);

        return Result<Purchase>.Success(purchase);
    }

    public async Task<Purchase?> GetPurchaseAsync(Guid householdId, Guid purchaseId, CancellationToken cancellationToken = default)
    {
        return await _db.Purchases
            .Include(p => p.Items)
            .AsNoTracking()
            .FirstOrDefaultAsync(p => p.PurchaseId == purchaseId && p.HouseholdId == householdId, cancellationToken);
    }

    public async Task<IReadOnlyList<Purchase>> GetHouseholdPurchasesAsync(Guid householdId, CancellationToken cancellationToken = default)
    {
        return await _db.Purchases
            .Include(p => p.Items)
            .AsNoTracking()
            .Where(p => p.HouseholdId == householdId)
            .OrderByDescending(p => p.PurchasedUtc)
            .ToListAsync(cancellationToken);
    }

    private Task<Purchase?> FindPurchaseForHouseholdAsync(Guid householdId, Guid purchaseId, CancellationToken cancellationToken)
    {
        return _db.Purchases
            .Include(p => p.Items)
            .FirstOrDefaultAsync(p => p.PurchaseId == purchaseId && p.HouseholdId == householdId, cancellationToken);
    }
}
