using CartWise.Application.DTOs;
using CartWise.Application.Services;
using CartWise.Domain.Entities;
using CartWise.Domain.Enums;
using CartWise.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace CartWise.Application.Tests;

public class ProductServiceTests : IDisposable
{
    private static readonly DateTime Now = new(2026, 8, 10, 12, 0, 0, DateTimeKind.Utc);

    private readonly CartWiseDbContext _db;
    private readonly ProductService _sut;

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

        _db = new CartWiseDbContext(options);
        _sut = new ProductService(_db, [], TimeProvider.System);
    }

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

    [Fact]
    public async Task SearchAsync_ReturnsMatchingProductsWithBrandName()
    {
        var brand = new Brand("Jif");
        var product = new Product("Jif Creamy Peanut Butter", ProductSourceType.Manual, Now, brandId: brand.BrandId, sizeValue: 16m, sizeUnit: "oz");
        _db.Brands.Add(brand);
        _db.Products.Add(product);
        await _db.SaveChangesAsync();

        var results = await _sut.SearchAsync("peanut");

        var result = Assert.Single(results);
        Assert.Equal("Jif Creamy Peanut Butter", result.Name);
        Assert.Equal("Jif", result.BrandName);
        Assert.Equal("16 oz", result.PackageDisplay);
    }

    [Fact]
    public async Task SearchAsync_ReturnsEmptyForBlankQuery()
    {
        var results = await _sut.SearchAsync("   ");

        Assert.Empty(results);
    }

    [Fact]
    public async Task SearchAsync_ReturnsEmptyWhenNothingMatches()
    {
        _db.Products.Add(new Product("Whole Milk", ProductSourceType.Manual, Now));
        await _db.SaveChangesAsync();

        var results = await _sut.SearchAsync("cereal");

        Assert.Empty(results);
    }

    [Fact]
    public async Task GetDetailsAsync_ReturnsNullWhenProductDoesNotExist()
    {
        var details = await _sut.GetDetailsAsync(Guid.NewGuid());

        Assert.Null(details);
    }

    [Fact]
    public async Task GetDetailsAsync_ReturnsBrandConceptAndIdentifiers()
    {
        var brand = new Brand("Jif");
        var concept = new GroceryConcept("Peanut Butter", Now);
        var product = new Product("Jif Creamy 16 oz", ProductSourceType.Manual, Now, groceryConceptId: concept.GroceryConceptId, brandId: brand.BrandId);
        product.AddIdentifier(ProductIdentifierType.UpcA, "051500255516");

        _db.Brands.Add(brand);
        _db.GroceryConcepts.Add(concept);
        _db.Products.Add(product);
        await _db.SaveChangesAsync();

        var details = await _sut.GetDetailsAsync(product.ProductId);

        Assert.NotNull(details);
        Assert.Equal("Jif", details!.BrandName);
        Assert.Equal("Peanut Butter", details.ConceptName);
        Assert.Equal("051500255516", Assert.Single(details.BarcodeIdentifiers));
    }

    [Fact]
    public async Task ResolveBarcodeAsync_ReturnsLocalProductWithoutCallingProvider()
    {
        var product = new Product("Whole Milk", ProductSourceType.Manual, Now);
        product.AddIdentifier(ProductIdentifierType.UpcA, "111111111111");
        _db.Products.Add(product);
        await _db.SaveChangesAsync();

        var sut = new ProductService(_db, [], TimeProvider.System);

        var result = await sut.ResolveBarcodeAsync("111111111111");

        Assert.True(result.IsSuccess);
        Assert.Equal("Whole Milk", result.Value!.Name);
    }

    [Fact]
    public async Task ResolveBarcodeAsync_FallsBackToProviderAndPersistsResult()
    {
        var provider = new FakeProductDataProvider(new Dictionary<string, ProductLookupResult>
        {
            ["222222222222"] = new ProductLookupResult
            {
                Name = "Imported Cereal",
                BrandName = "Acme",
                SizeValue = 12m,
                SizeUnit = "oz",
                SourceExternalId = "off-123"
            }
        });
        var sut = new ProductService(_db, [provider], TimeProvider.System);

        var result = await sut.ResolveBarcodeAsync("222222222222");

        Assert.True(result.IsSuccess);
        Assert.Equal("Imported Cereal", result.Value!.Name);
        Assert.Equal("Acme", result.Value.BrandName);

        var persisted = await _db.Products.AsNoTracking().SingleAsync();
        Assert.Equal(ProductSourceType.OpenFoodFacts, persisted.SourceType);
        Assert.Equal("off-123", persisted.SourceExternalId);
    }

    [Fact]
    public async Task ResolveBarcodeAsync_FailsWhenNoLocalMatchAndNoProviderResult()
    {
        var provider = new FakeProductDataProvider(new Dictionary<string, ProductLookupResult>());
        var sut = new ProductService(_db, [provider], TimeProvider.System);

        var result = await sut.ResolveBarcodeAsync("999999999999");

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task ResolveBarcodeAsync_FailsWhenBarcodeIsMissing()
    {
        var result = await _sut.ResolveBarcodeAsync("   ");

        Assert.False(result.IsSuccess);
    }
}
