using FluentAssertions;
using LittleShop.Data;
using LittleShop.DTOs;
using LittleShop.Models;
using LittleShop.Services;
using LittleShop.Enums;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Moq;
using Xunit;

namespace LittleShop.Tests.Unit;

public class ProductServiceTests : IDisposable
{
    private readonly LittleShopContext _context;
    private readonly IProductService _productService;
    private readonly Mock<IWebHostEnvironment> _mockEnvironment;

    public ProductServiceTests()
    {
        // Set up in-memory database
        var options = new DbContextOptionsBuilder<LittleShopContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;
        _context = new LittleShopContext(options);

        // Set up mock environment
        _mockEnvironment = new Mock<IWebHostEnvironment>();
        _mockEnvironment.Setup(e => e.WebRootPath).Returns("/test/wwwroot");

        // Create service
        _productService = new ProductService(_context, _mockEnvironment.Object);
    }

    [Fact]
    public async Task GetAllProductsAsync_ReturnsAllProducts()
    {
        // Arrange
        var category = await CreateTestCategory();
        var products = new[]
        {
            CreateTestProduct("Product 1", category.Id, 10.00m),
            CreateTestProduct("Product 2", category.Id, 20.00m),
            CreateTestProduct("Product 3", category.Id, 30.00m, isActive: false)
        };
        
        _context.Products.AddRange(products);
        await _context.SaveChangesAsync();

        // Act
        var result = await _productService.GetAllProductsAsync();

        // Assert - only active products should be returned
        result.Should().HaveCount(2);
        result.Should().Contain(p => p.Name == "Product 1");
        result.Should().Contain(p => p.Name == "Product 2");
        result.Should().NotContain(p => p.Name == "Product 3"); // Inactive product should not be returned
    }

    [Fact]
    public async Task GetProductByIdAsync_WithValidId_ReturnsProduct()
    {
        // Arrange
        var category = await CreateTestCategory();
        var productId = Guid.NewGuid();
        var product = new Product 
        { 
            Id = productId, 
            Name = "Test Product", 
            Description = "Test Description",
            Price = 99.99m,
            CategoryId = category.Id,
            IsActive = true,
            Weight = 1.5m,
            WeightUnit = ProductWeightUnit.Kilograms
        };
        
        _context.Products.Add(product);
        await _context.SaveChangesAsync();

        // Act
        var result = await _productService.GetProductByIdAsync(productId);

        // Assert
        result.Should().NotBeNull();
        result!.Id.Should().Be(productId);
        result.Name.Should().Be("Test Product");
        result.Price.Should().Be(99.99m);
    }

    [Fact]
    public async Task GetProductsByCategoryAsync_ReturnsOnlyProductsInCategory()
    {
        // Arrange
        var category1 = await CreateTestCategory("Category 1");
        var category2 = await CreateTestCategory("Category 2");
        
        var products = new[]
        {
            CreateTestProduct("Product 1", category1.Id, 10.00m),
            CreateTestProduct("Product 2", category1.Id, 20.00m),
            CreateTestProduct("Product 3", category2.Id, 30.00m)
        };
        
        _context.Products.AddRange(products);
        await _context.SaveChangesAsync();

        // Act
        var result = await _productService.GetProductsByCategoryAsync(category1.Id);

        // Assert
        result.Should().HaveCount(2);
        result.Should().OnlyContain(p => p.CategoryId == category1.Id);
    }

    [Fact]
    public async Task CreateProductAsync_WithValidData_CreatesProduct()
    {
        // Arrange
        var category = await CreateTestCategory();
        var createDto = new CreateProductDto
        {
            Name = "New Product",
            Description = "New Description",
            Price = 49.99m,
            CategoryId = category.Id,
            Weight = 2.5m,
            WeightUnit = ProductWeightUnit.Kilograms
        };

        // Act
        var result = await _productService.CreateProductAsync(createDto);

        // Assert
        result.Should().NotBeNull();
        result.Name.Should().Be("New Product");
        result.Price.Should().Be(49.99m);
        result.IsActive.Should().BeTrue();

        // Verify in database
        var dbProduct = await _context.Products.FindAsync(result.Id);
        dbProduct.Should().NotBeNull();
        dbProduct!.Name.Should().Be("New Product");
    }

    [Fact]
    public async Task UpdateProductAsync_WithValidData_UpdatesProduct()
    {
        // Arrange
        var category = await CreateTestCategory();
        var productId = Guid.NewGuid();
        var product = new Product 
        { 
            Id = productId, 
            Name = "Original Name", 
            Description = "Original Description",
            Price = 10.00m,
            CategoryId = category.Id,
            IsActive = true,
            Weight = 1.0m,
            WeightUnit = ProductWeightUnit.Kilograms
        };
        
        _context.Products.Add(product);
        await _context.SaveChangesAsync();

        var updateDto = new UpdateProductDto
        {
            Name = "Updated Name",
            Description = "Updated Description",
            Price = 20.00m,
            CategoryId = category.Id,
            Weight = 2.0m,
            WeightUnit = ProductWeightUnit.Pounds,
            IsActive = false
        };

        // Act
        var result = await _productService.UpdateProductAsync(productId, updateDto);

        // Assert
        result.Should().BeTrue();

        // Verify in database
        var dbProduct = await _context.Products.FindAsync(productId);
        dbProduct!.Name.Should().Be("Updated Name");
        dbProduct.Price.Should().Be(20.00m);
        dbProduct.Weight.Should().Be(2.0m);
        dbProduct.WeightUnit.Should().Be(ProductWeightUnit.Pounds);
        dbProduct.IsActive.Should().BeFalse();
    }

    [Fact]
    public async Task DeleteProductAsync_WithValidId_DeletesProduct()
    {
        // Arrange
        var category = await CreateTestCategory();
        var productId = Guid.NewGuid();
        var product = CreateTestProduct("To Delete", category.Id, 10.00m);
        product.Id = productId;
        
        _context.Products.Add(product);
        await _context.SaveChangesAsync();

        // Act
        var result = await _productService.DeleteProductAsync(productId);

        // Assert
        result.Should().BeTrue();

        // Verify in database - soft delete means IsActive = false
        var dbProduct = await _context.Products.FindAsync(productId);
        dbProduct.Should().NotBeNull();
        dbProduct!.IsActive.Should().BeFalse();
    }

    [Fact]
    public async Task AddProductPhotoAsync_AddsPhotoToProduct()
    {
        // Arrange
        var category = await CreateTestCategory();
        var productId = Guid.NewGuid();
        var product = CreateTestProduct("Product", category.Id, 10.00m);
        product.Id = productId;
        
        _context.Products.Add(product);
        await _context.SaveChangesAsync();

        var photoDto = new CreateProductPhotoDto
        {
            ProductId = productId,
            PhotoUrl = "/uploads/test-photo.jpg",
            AltText = "Test Photo",
            DisplayOrder = 1
        };

        // Act
        var result = await _productService.AddProductPhotoAsync(photoDto);

        // Assert
        result.Should().NotBeNull();
        result.FilePath.Should().Contain("test-photo.jpg");
        result.AltText.Should().Be("Test Photo");

        // Verify in database
        var dbProduct = await _context.Products
            .Include(p => p.Photos)
            .FirstOrDefaultAsync(p => p.Id == productId);
        dbProduct!.Photos.Should().HaveCount(1);
    }

    [Fact]
    public async Task GetProductsBySearchAsync_ReturnsMatchingProducts()
    {
        // Arrange
        var category = await CreateTestCategory();
        var products = new[]
        {
            CreateTestProduct("Laptop Computer", category.Id, 999.00m),
            CreateTestProduct("Desktop Computer", category.Id, 799.00m),
            CreateTestProduct("Mouse Pad", category.Id, 9.99m)
        };
        
        _context.Products.AddRange(products);
        await _context.SaveChangesAsync();

        // Act - Search for "Computer"
        var result = await _productService.SearchProductsAsync("Computer");

        // Assert
        result.Should().HaveCount(2);
        result.Should().Contain(p => p.Name.Contains("Computer"));
        result.Should().NotContain(p => p.Name == "Mouse Pad");
    }

    private async Task<Category> CreateTestCategory(string name = "Test Category")
    {
        var category = new Category 
        { 
            Id = Guid.NewGuid(), 
            Name = name, 
            Description = "Test Description", 
            IsActive = true 
        };
        
        _context.Categories.Add(category);
        await _context.SaveChangesAsync();
        return category;
    }

    private Product CreateTestProduct(string name, Guid categoryId, decimal price, bool isActive = true)
    {
        return new Product
        {
            Id = Guid.NewGuid(),
            Name = name,
            Description = $"Description for {name}",
            Price = price,
            CategoryId = categoryId,
            IsActive = isActive,
            Weight = 1.0m,
            WeightUnit = ProductWeightUnit.Kilograms
        };
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}