using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Products.Entities;
using TakeoutSaaS.Domain.Products.Enums;
using TakeoutSaaS.Domain.Products.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;

namespace TakeoutSaaS.Infrastructure.App.Repositories;

/// <summary>
/// 商品聚合的 EF Core 仓储实现。
/// </summary>
/// <remarks>
/// 初始化仓储。
/// </remarks>
public sealed class EfProductRepository(TakeoutAppDbContext context) : IProductRepository
{
    /// <inheritdoc />
    public Task<Product?> FindByIdAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
    {
        return context.Products
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.Id == productId)
            .FirstOrDefaultAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<Product>> SearchAsync(long tenantId, long? storeId, long? categoryId, ProductStatus? status, CancellationToken cancellationToken = default, DateTime? updatedAfter = null)
    {
        var query = context.Products
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId);

        if (storeId.HasValue)
        {
            query = query.Where(x => x.StoreId == storeId.Value);
        }

        if (categoryId.HasValue)
        {
            query = query.Where(x => x.CategoryId == categoryId.Value);
        }

        if (status.HasValue)
        {
            query = query.Where(x => x.Status == status.Value);
        }

        if (updatedAfter.HasValue)
        {
            query = query.Where(x => (x.UpdatedAt ?? x.CreatedAt) >= updatedAfter.Value);
        }

        var products = await query
            .OrderBy(x => x.Name)
            .ToListAsync(cancellationToken);

        return products;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductCategory>> GetCategoriesAsync(long tenantId, CancellationToken cancellationToken = default)
    {
        var categories = await context.ProductCategories
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId)
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);

        return categories;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductCategory>> GetCategoriesByStoreAsync(long tenantId, long storeId, bool onlyEnabled = true, CancellationToken cancellationToken = default)
    {
        var query = context.ProductCategories
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.StoreId == storeId);
        if (onlyEnabled)
        {
            query = query.Where(x => x.IsEnabled);
        }
        var categories = await query
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);
        return categories;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductSku>> GetSkusAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
    {
        var skus = await context.ProductSkus
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.ProductId == productId)
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);

        return skus;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductSku>> GetSkusByProductIdsAsync(IReadOnlyCollection<long> productIds, long tenantId, CancellationToken cancellationToken = default)
    {
        if (productIds.Count == 0)
        {
            return Array.Empty<ProductSku>();
        }
        var skus = await context.ProductSkus
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && productIds.Contains(x.ProductId))
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);
        return skus;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductAddonGroup>> GetAddonGroupsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
    {
        var groups = await context.ProductAddonGroups
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.ProductId == productId)
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);

        return groups;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductAddonGroup>> GetAddonGroupsByProductIdsAsync(IReadOnlyCollection<long> productIds, long tenantId, CancellationToken cancellationToken = default)
    {
        if (productIds.Count == 0)
        {
            return Array.Empty<ProductAddonGroup>();
        }
        var groups = await context.ProductAddonGroups
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && productIds.Contains(x.ProductId))
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);
        return groups;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductAddonOption>> GetAddonOptionsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
    {
        var groupIds = await context.ProductAddonGroups
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.ProductId == productId)
            .Select(x => x.Id)
            .ToListAsync(cancellationToken);

        if (groupIds.Count == 0)
        {
            return Array.Empty<ProductAddonOption>();
        }

        var options = await context.ProductAddonOptions
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && groupIds.Contains(x.AddonGroupId))
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);

        return options;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductAddonOption>> GetAddonOptionsByGroupIdsAsync(IReadOnlyCollection<long> addonGroupIds, long tenantId, CancellationToken cancellationToken = default)
    {
        if (addonGroupIds.Count == 0)
        {
            return Array.Empty<ProductAddonOption>();
        }
        var options = await context.ProductAddonOptions
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && addonGroupIds.Contains(x.AddonGroupId))
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);
        return options;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductAttributeGroup>> GetAttributeGroupsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
    {
        var groups = await context.ProductAttributeGroups
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.ProductId == productId)
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);

        return groups;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductAttributeGroup>> GetAttributeGroupsByProductIdsAsync(IReadOnlyCollection<long> productIds, long tenantId, CancellationToken cancellationToken = default)
    {
        if (productIds.Count == 0)
        {
            return Array.Empty<ProductAttributeGroup>();
        }
        var groups = await context.ProductAttributeGroups
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && productIds.Contains(x.ProductId))
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);
        return groups;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductAttributeOption>> GetAttributeOptionsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
    {
        var groupIds = await context.ProductAttributeGroups
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.ProductId == productId)
            .Select(x => x.Id)
            .ToListAsync(cancellationToken);

        if (groupIds.Count == 0)
        {
            return Array.Empty<ProductAttributeOption>();
        }

        var options = await context.ProductAttributeOptions
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && groupIds.Contains(x.AttributeGroupId))
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);

        return options;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductAttributeOption>> GetAttributeOptionsByGroupIdsAsync(IReadOnlyCollection<long> attributeGroupIds, long tenantId, CancellationToken cancellationToken = default)
    {
        if (attributeGroupIds.Count == 0)
        {
            return Array.Empty<ProductAttributeOption>();
        }
        var options = await context.ProductAttributeOptions
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && attributeGroupIds.Contains(x.AttributeGroupId))
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);
        return options;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductMediaAsset>> GetMediaAssetsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
    {
        var assets = await context.ProductMediaAssets
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.ProductId == productId)
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);

        return assets;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductMediaAsset>> GetMediaAssetsByProductIdsAsync(IReadOnlyCollection<long> productIds, long tenantId, CancellationToken cancellationToken = default)
    {
        if (productIds.Count == 0)
        {
            return Array.Empty<ProductMediaAsset>();
        }
        var assets = await context.ProductMediaAssets
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && productIds.Contains(x.ProductId))
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);
        return assets;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductPricingRule>> GetPricingRulesAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
    {
        var rules = await context.ProductPricingRules
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.ProductId == productId)
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);

        return rules;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<ProductPricingRule>> GetPricingRulesByProductIdsAsync(IReadOnlyCollection<long> productIds, long tenantId, CancellationToken cancellationToken = default)
    {
        if (productIds.Count == 0)
        {
            return Array.Empty<ProductPricingRule>();
        }
        var rules = await context.ProductPricingRules
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && productIds.Contains(x.ProductId))
            .OrderBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);
        return rules;
    }

    /// <inheritdoc />
    public Task AddCategoryAsync(ProductCategory category, CancellationToken cancellationToken = default)
    {
        return context.ProductCategories.AddAsync(category, cancellationToken).AsTask();
    }

    /// <inheritdoc />
    public Task AddProductAsync(Product product, CancellationToken cancellationToken = default)
    {
        return context.Products.AddAsync(product, cancellationToken).AsTask();
    }

    /// <inheritdoc />
    public Task AddSkusAsync(IEnumerable<ProductSku> skus, CancellationToken cancellationToken = default)
    {
        return context.ProductSkus.AddRangeAsync(skus, cancellationToken);
    }

    /// <inheritdoc />
    public Task AddAddonGroupsAsync(IEnumerable<ProductAddonGroup> groups, IEnumerable<ProductAddonOption> options, CancellationToken cancellationToken = default)
    {
        var addGroupsTask = context.ProductAddonGroups.AddRangeAsync(groups, cancellationToken);
        var addOptionsTask = context.ProductAddonOptions.AddRangeAsync(options, cancellationToken);
        return Task.WhenAll(addGroupsTask, addOptionsTask);
    }

    /// <inheritdoc />
    public Task AddAttributeGroupsAsync(IEnumerable<ProductAttributeGroup> groups, IEnumerable<ProductAttributeOption> options, CancellationToken cancellationToken = default)
    {
        var addGroupsTask = context.ProductAttributeGroups.AddRangeAsync(groups, cancellationToken);
        var addOptionsTask = context.ProductAttributeOptions.AddRangeAsync(options, cancellationToken);
        return Task.WhenAll(addGroupsTask, addOptionsTask);
    }

    /// <inheritdoc />
    public Task AddMediaAssetsAsync(IEnumerable<ProductMediaAsset> assets, CancellationToken cancellationToken = default)
    {
        return context.ProductMediaAssets.AddRangeAsync(assets, cancellationToken);
    }

    /// <inheritdoc />
    public Task AddPricingRulesAsync(IEnumerable<ProductPricingRule> rules, CancellationToken cancellationToken = default)
    {
        return context.ProductPricingRules.AddRangeAsync(rules, cancellationToken);
    }

    /// <inheritdoc />
    public Task SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        return context.SaveChangesAsync(cancellationToken);
    }

    /// <inheritdoc />
    public Task UpdateProductAsync(Product product, CancellationToken cancellationToken = default)
    {
        context.Products.Update(product);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public async Task DeleteProductAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
    {
        await RemovePricingRulesAsync(productId, tenantId, cancellationToken);
        await RemoveMediaAssetsAsync(productId, tenantId, cancellationToken);
        await RemoveAttributeGroupsAsync(productId, tenantId, cancellationToken);
        await RemoveAddonGroupsAsync(productId, tenantId, cancellationToken);
        await RemoveSkusAsync(productId, tenantId, cancellationToken);

        var existing = await context.Products
            .Where(x => x.TenantId == tenantId && x.Id == productId)
            .FirstOrDefaultAsync(cancellationToken);

        if (existing == null)
        {
            return;
        }

        context.Products.Remove(existing);
    }

    /// <inheritdoc />
    public Task UpdateCategoryAsync(ProductCategory category, CancellationToken cancellationToken = default)
    {
        context.ProductCategories.Update(category);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public async Task DeleteCategoryAsync(long categoryId, long tenantId, CancellationToken cancellationToken = default)
    {
        var existing = await context.ProductCategories
            .Where(x => x.TenantId == tenantId && x.Id == categoryId)
            .FirstOrDefaultAsync(cancellationToken);

        if (existing == null)
        {
            return;
        }

        context.ProductCategories.Remove(existing);
    }

    /// <inheritdoc />
    public async Task RemoveSkusAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
    {
        var skus = await context.ProductSkus
            .Where(x => x.TenantId == tenantId && x.ProductId == productId)
            .ToListAsync(cancellationToken);

        if (skus.Count == 0)
        {
            return;
        }

        context.ProductSkus.RemoveRange(skus);
    }

    /// <inheritdoc />
    public async Task RemoveAddonGroupsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
    {
        var groupIds = await context.ProductAddonGroups
            .Where(x => x.TenantId == tenantId && x.ProductId == productId)
            .Select(x => x.Id)
            .ToListAsync(cancellationToken);

        if (groupIds.Count == 0)
        {
            return;
        }

        var options = await context.ProductAddonOptions
            .Where(x => x.TenantId == tenantId && groupIds.Contains(x.AddonGroupId))
            .ToListAsync(cancellationToken);

        if (options.Count > 0)
        {
            context.ProductAddonOptions.RemoveRange(options);
        }

        var groups = await context.ProductAddonGroups
            .Where(x => groupIds.Contains(x.Id))
            .ToListAsync(cancellationToken);

        if (groups.Count > 0)
        {
            context.ProductAddonGroups.RemoveRange(groups);
        }
    }

    /// <inheritdoc />
    public async Task RemoveAttributeGroupsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
    {
        var groupIds = await context.ProductAttributeGroups
            .Where(x => x.TenantId == tenantId && x.ProductId == productId)
            .Select(x => x.Id)
            .ToListAsync(cancellationToken);

        if (groupIds.Count == 0)
        {
            return;
        }

        var options = await context.ProductAttributeOptions
            .Where(x => x.TenantId == tenantId && groupIds.Contains(x.AttributeGroupId))
            .ToListAsync(cancellationToken);

        if (options.Count > 0)
        {
            context.ProductAttributeOptions.RemoveRange(options);
        }

        var groups = await context.ProductAttributeGroups
            .Where(x => groupIds.Contains(x.Id))
            .ToListAsync(cancellationToken);

        if (groups.Count > 0)
        {
            context.ProductAttributeGroups.RemoveRange(groups);
        }
    }

    /// <inheritdoc />
    public async Task RemoveMediaAssetsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
    {
        var assets = await context.ProductMediaAssets
            .Where(x => x.TenantId == tenantId && x.ProductId == productId)
            .ToListAsync(cancellationToken);

        if (assets.Count == 0)
        {
            return;
        }

        context.ProductMediaAssets.RemoveRange(assets);
    }

    /// <inheritdoc />
    public async Task RemovePricingRulesAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
    {
        var rules = await context.ProductPricingRules
            .Where(x => x.TenantId == tenantId && x.ProductId == productId)
            .ToListAsync(cancellationToken);

        if (rules.Count == 0)
        {
            return;
        }

        context.ProductPricingRules.RemoveRange(rules);
    }
}
