using ERPCore.Domain.Entities;
using ERPCore.Domain.Enums;
using ERPCore.Dtos.Sales;
using ERPCore.Repositories.Interfaces;
using ERPCore.Services.Interfaces;
using Microsoft.EntityFrameworkCore;

namespace ERPCore.Services;

public sealed class SalesPromotionSuggestionService : ISalesPromotionSuggestionService
{
    private readonly IRepository<SalesSlip> _slips;
    private readonly IRepository<Item> _items;

    public SalesPromotionSuggestionService(IRepository<SalesSlip> slips, IRepository<Item> items)
    {
        _slips = slips;
        _items = items;
    }

    public async Task<SalesFreeIssueSuggestionDto?> GetFreeIssueSuggestionsAsync(int salesSlipId, CancellationToken ct = default)
    {
        var slip = await _slips.Query().AsNoTracking().Include(x => x.Lines)
            .FirstOrDefaultAsync(x => x.SalesSlipId == salesSlipId, ct);

        if (slip is null) return null;

        var freeIssueLines = slip.Lines
            .Where(x => x.IsFreeIssue || x.FreeQty > 0m)
            .ToList();

        if (freeIssueLines.Count == 0)
            return new SalesFreeIssueSuggestionDto(slip.SalesSlipId, slip.SlipNo, slip.SlipDate, Array.Empty<SalesFreeIssueSuggestionLineDto>());

        var itemIds = freeIssueLines.Select(x => x.ItemId).Distinct().ToList();
        var candidateItems = await _items.Query().AsNoTracking()
            .Where(x => itemIds.Contains(x.ItemId) && x.Status == EntityStatus.Active)
            .ToListAsync(ct);

        var byItemId = candidateItems.ToDictionary(x => x.ItemId);
        var suggestions = new List<SalesFreeIssueSuggestionLineDto>();

        foreach (var line in freeIssueLines)
        {
            if (!byItemId.TryGetValue(line.ItemId, out var item)) continue;

            var rewardOptions = new List<SalesFreeIssueRewardOptionDto>
            {
                new(item.ItemId, item.Sku, item.Name, item.SalePrice)
            };

            var alternates = await _items.Query().AsNoTracking()
                .Where(x => x.Status == EntityStatus.Active && x.CategoryId == item.CategoryId && x.ItemId != item.ItemId)
                .OrderBy(x => x.Name)
                .Take(3)
                .Select(x => new SalesFreeIssueRewardOptionDto(x.ItemId, x.Sku, x.Name, x.SalePrice))
                .ToListAsync(ct);

            rewardOptions.AddRange(alternates.Where(x => rewardOptions.All(r => r.ItemId != x.ItemId)));

            suggestions.Add(new SalesFreeIssueSuggestionLineDto(
                line.SalesSlipLineId,
                item.ItemId,
                item.Sku,
                item.Name,
                line.Qty,
                line.FreeQty,
                line.Qty,
                rewardOptions));
        }

        return suggestions.Count == 0
            ? new SalesFreeIssueSuggestionDto(slip.SalesSlipId, slip.SlipNo, slip.SlipDate, Array.Empty<SalesFreeIssueSuggestionLineDto>())
            : new SalesFreeIssueSuggestionDto(slip.SalesSlipId, slip.SlipNo, slip.SlipDate, suggestions);
    }
}
