using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Finance.Entities;
using TakeoutSaaS.Domain.Finance.Enums;
using TakeoutSaaS.Domain.Finance.Models;
using TakeoutSaaS.Domain.Finance.Repositories;
using TakeoutSaaS.Domain.Payments.Enums;
using TakeoutSaaS.Infrastructure.App.Persistence;

namespace TakeoutSaaS.Infrastructure.App.Repositories;

/// <summary>
/// 成本管理 EF Core 仓储实现。
/// </summary>
public sealed class EfFinanceCostRepository(TakeoutAppDbContext context) : IFinanceCostRepository
{
    private static readonly FinanceCostCategory[] CategoryOrder =
    [
        FinanceCostCategory.FoodMaterial,
        FinanceCostCategory.Labor,
        FinanceCostCategory.FixedExpense,
        FinanceCostCategory.PackagingConsumable
    ];

    /// <inheritdoc />
    public async Task<FinanceCostMonthSnapshot> GetMonthSnapshotAsync(
        long tenantId,
        FinanceCostDimension dimension,
        long? storeId,
        DateTime costMonth,
        CancellationToken cancellationToken = default)
    {
        // 1. 归一化月份并加载分类快照。
        var normalizedMonth = NormalizeMonthStart(costMonth);
        var normalizedStoreId = NormalizeStoreId(dimension, storeId);
        var categories = await GetCategorySnapshotsAsync(
            tenantId,
            dimension,
            normalizedStoreId,
            normalizedMonth,
            cancellationToken);

        // 2. 读取本月营业额（真实订单与支付记录聚合）。
        var monthRevenue = await GetRevenueByMonthAsync(
            tenantId,
            dimension,
            normalizedStoreId,
            normalizedMonth,
            cancellationToken);

        return new FinanceCostMonthSnapshot
        {
            Dimension = dimension,
            StoreId = normalizedStoreId,
            CostMonth = normalizedMonth,
            MonthRevenue = monthRevenue,
            Categories = categories
        };
    }

    /// <inheritdoc />
    public async Task SaveMonthSnapshotAsync(
        long tenantId,
        FinanceCostDimension dimension,
        long? storeId,
        DateTime costMonth,
        IReadOnlyList<FinanceCostCategorySnapshot> categories,
        CancellationToken cancellationToken = default)
    {
        // 1. 归一化入参与分类数据。
        var normalizedMonth = NormalizeMonthStart(costMonth);
        var normalizedStoreId = NormalizeStoreId(dimension, storeId);
        var normalizedCategories = NormalizeCategoriesForSave(categories);

        // 2. 删除同维度同月份历史记录（先删明细，再删汇总）。
        var existingEntries = await context.FinanceCostEntries
            .Where(item =>
                item.TenantId == tenantId &&
                item.Dimension == dimension &&
                item.CostMonth == normalizedMonth &&
                ((dimension == FinanceCostDimension.Store && item.StoreId == normalizedStoreId) ||
                 (dimension == FinanceCostDimension.Tenant && item.StoreId == null)))
            .ToListAsync(cancellationToken);

        var existingEntryIds = existingEntries.Select(item => item.Id).ToList();
        if (existingEntryIds.Count > 0)
        {
            var existingItems = await context.FinanceCostEntryItems
                .Where(item => existingEntryIds.Contains(item.EntryId))
                .ToListAsync(cancellationToken);
            context.FinanceCostEntryItems.RemoveRange(existingItems);
        }

        context.FinanceCostEntries.RemoveRange(existingEntries);
        await context.SaveChangesAsync(cancellationToken);

        // 3. 新增汇总行并持久化，拿到主键。
        var newEntries = normalizedCategories
            .Select(item => new FinanceCostEntry
            {
                TenantId = tenantId,
                Dimension = dimension,
                StoreId = normalizedStoreId,
                CostMonth = normalizedMonth,
                Category = item.Category,
                TotalAmount = RoundAmount(item.TotalAmount)
            })
            .ToList();

        if (newEntries.Count > 0)
        {
            await context.FinanceCostEntries.AddRangeAsync(newEntries, cancellationToken);
            await context.SaveChangesAsync(cancellationToken);
        }

        // 4. 写入明细项并持久化。
        var entryIdMap = newEntries.ToDictionary(item => item.Category, item => item.Id);
        var newItems = new List<FinanceCostEntryItem>();
        foreach (var category in normalizedCategories)
        {
            if (!entryIdMap.TryGetValue(category.Category, out var entryId))
            {
                continue;
            }

            foreach (var detail in category.Items.OrderBy(item => item.SortOrder).ThenBy(item => item.ItemName))
            {
                newItems.Add(new FinanceCostEntryItem
                {
                    TenantId = tenantId,
                    EntryId = entryId,
                    Dimension = dimension,
                    StoreId = normalizedStoreId,
                    CostMonth = normalizedMonth,
                    Category = category.Category,
                    ItemName = detail.ItemName.Trim(),
                    Amount = RoundAmount(detail.Amount),
                    Quantity = detail.Quantity.HasValue ? RoundAmount(detail.Quantity.Value) : null,
                    UnitPrice = detail.UnitPrice.HasValue ? RoundAmount(detail.UnitPrice.Value) : null,
                    SortOrder = detail.SortOrder
                });
            }
        }

        if (newItems.Count > 0)
        {
            await context.FinanceCostEntryItems.AddRangeAsync(newItems, cancellationToken);
        }

        await context.SaveChangesAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task<FinanceCostAnalysisSnapshot> GetAnalysisSnapshotAsync(
        long tenantId,
        FinanceCostDimension dimension,
        long? storeId,
        DateTime costMonth,
        int trendMonthCount,
        CancellationToken cancellationToken = default)
    {
        // 1. 归一化参数并生成趋势月份序列。
        var normalizedMonth = NormalizeMonthStart(costMonth);
        var normalizedStoreId = NormalizeStoreId(dimension, storeId);
        var normalizedTrendCount = Math.Clamp(trendMonthCount, 3, 12);
        var trendMonths = BuildTrendMonths(normalizedMonth, normalizedTrendCount);

        // 2. 读取当前月分类、营业额、已支付订单量。
        var currentCategories = await GetCategorySnapshotsAsync(
            tenantId,
            dimension,
            normalizedStoreId,
            normalizedMonth,
            cancellationToken);
        var currentTotalCost = RoundAmount(currentCategories.Sum(item => item.TotalAmount));
        var currentFoodAmount = RoundAmount(currentCategories
            .FirstOrDefault(item => item.Category == FinanceCostCategory.FoodMaterial)
            ?.TotalAmount ?? 0m);
        var currentRevenue = await GetRevenueByMonthAsync(
            tenantId,
            dimension,
            normalizedStoreId,
            normalizedMonth,
            cancellationToken);
        var currentPaidOrderCount = await GetPaidOrderCountByMonthAsync(
            tenantId,
            dimension,
            normalizedStoreId,
            normalizedMonth,
            cancellationToken);

        // 3. 计算环比变化（与上月总成本对比）。
        var previousMonth = normalizedMonth.AddMonths(-1);
        var previousCategories = await GetCategorySnapshotsAsync(
            tenantId,
            dimension,
            normalizedStoreId,
            previousMonth,
            cancellationToken);
        var previousTotalCost = RoundAmount(previousCategories.Sum(item => item.TotalAmount));
        var monthOnMonthChangeRate = CalculateMonthOnMonthChangeRate(currentTotalCost, previousTotalCost);

        // 4. 组装趋势与明细表行。
        var trends = new List<FinanceCostTrendSnapshot>(trendMonths.Count);
        var detailRows = new List<FinanceCostMonthlyDetailSnapshot>(trendMonths.Count);

        foreach (var month in trendMonths)
        {
            var monthCategories = await GetCategorySnapshotsAsync(
                tenantId,
                dimension,
                normalizedStoreId,
                month,
                cancellationToken);
            var monthRevenue = await GetRevenueByMonthAsync(
                tenantId,
                dimension,
                normalizedStoreId,
                month,
                cancellationToken);

            var foodAmount = RoundAmount(monthCategories
                .FirstOrDefault(item => item.Category == FinanceCostCategory.FoodMaterial)
                ?.TotalAmount ?? 0m);
            var laborAmount = RoundAmount(monthCategories
                .FirstOrDefault(item => item.Category == FinanceCostCategory.Labor)
                ?.TotalAmount ?? 0m);
            var fixedAmount = RoundAmount(monthCategories
                .FirstOrDefault(item => item.Category == FinanceCostCategory.FixedExpense)
                ?.TotalAmount ?? 0m);
            var packagingAmount = RoundAmount(monthCategories
                .FirstOrDefault(item => item.Category == FinanceCostCategory.PackagingConsumable)
                ?.TotalAmount ?? 0m);
            var monthTotalCost = RoundAmount(foodAmount + laborAmount + fixedAmount + packagingAmount);

            trends.Add(new FinanceCostTrendSnapshot
            {
                MonthStartUtc = month,
                TotalCost = monthTotalCost,
                Revenue = monthRevenue
            });

            detailRows.Add(new FinanceCostMonthlyDetailSnapshot
            {
                MonthStartUtc = month,
                FoodAmount = foodAmount,
                LaborAmount = laborAmount,
                FixedAmount = fixedAmount,
                PackagingAmount = packagingAmount,
                TotalCost = monthTotalCost,
                Revenue = monthRevenue
            });
        }

        return new FinanceCostAnalysisSnapshot
        {
            Dimension = dimension,
            StoreId = normalizedStoreId,
            CostMonth = normalizedMonth,
            CurrentTotalCost = currentTotalCost,
            CurrentFoodAmount = currentFoodAmount,
            CurrentRevenue = currentRevenue,
            CurrentPaidOrderCount = currentPaidOrderCount,
            MonthOnMonthChangeRate = monthOnMonthChangeRate,
            CurrentCategories = currentCategories,
            Trends = trends,
            DetailRows = detailRows
        };
    }

    private async Task<IReadOnlyList<FinanceCostCategorySnapshot>> GetCategorySnapshotsAsync(
        long tenantId,
        FinanceCostDimension dimension,
        long? storeId,
        DateTime month,
        CancellationToken cancellationToken)
    {
        // 1. 读取当月汇总与明细。
        var entryQuery = context.FinanceCostEntries
            .AsNoTracking()
            .Where(item =>
                item.TenantId == tenantId &&
                item.Dimension == dimension &&
                item.CostMonth == month &&
                ((dimension == FinanceCostDimension.Store && item.StoreId == storeId) ||
                 (dimension == FinanceCostDimension.Tenant && item.StoreId == null)));
        var entries = await entryQuery.ToListAsync(cancellationToken);

        var entryIds = entries.Select(item => item.Id).ToList();
        var items = entryIds.Count == 0
            ? []
            : await context.FinanceCostEntryItems
                .AsNoTracking()
                .Where(item => entryIds.Contains(item.EntryId))
                .OrderBy(item => item.SortOrder)
                .ThenBy(item => item.Id)
                .ToListAsync(cancellationToken);

        // 2. 按分类聚合，补齐默认分类顺序。
        var entryMap = entries.ToDictionary(item => item.Category, item => item);
        var itemGroupMap = items
            .GroupBy(item => item.Category)
            .ToDictionary(group => group.Key, group => group.ToList());

        return CategoryOrder.Select(category =>
        {
            var totalAmount = entryMap.TryGetValue(category, out var entry)
                ? RoundAmount(entry.TotalAmount)
                : 0m;
            var details = itemGroupMap.TryGetValue(category, out var group)
                ? group.Select(detail => new FinanceCostDetailItemSnapshot
                {
                    ItemId = detail.Id,
                    ItemName = detail.ItemName,
                    Amount = RoundAmount(detail.Amount),
                    Quantity = detail.Quantity.HasValue ? RoundAmount(detail.Quantity.Value) : null,
                    UnitPrice = detail.UnitPrice.HasValue ? RoundAmount(detail.UnitPrice.Value) : null,
                    SortOrder = detail.SortOrder
                }).ToList()
                : [];

            return new FinanceCostCategorySnapshot
            {
                Category = category,
                TotalAmount = totalAmount,
                Items = details
            };
        }).ToList();
    }

    private async Task<decimal> GetRevenueByMonthAsync(
        long tenantId,
        FinanceCostDimension dimension,
        long? storeId,
        DateTime monthStartUtc,
        CancellationToken cancellationToken)
    {
        var monthEnd = monthStartUtc.AddMonths(1);

        // 1. 聚合支付成功金额。
        var paidQuery =
            from payment in context.PaymentRecords.AsNoTracking()
            join order in context.Orders.AsNoTracking()
                on payment.OrderId equals order.Id
            where payment.TenantId == tenantId
                  && order.TenantId == tenantId
                  && payment.Status == PaymentStatus.Paid
                  && (payment.PaidAt ?? payment.CreatedAt) >= monthStartUtc
                  && (payment.PaidAt ?? payment.CreatedAt) < monthEnd
            select new
            {
                order.StoreId,
                payment.Amount
            };

        if (dimension == FinanceCostDimension.Store && storeId.HasValue)
        {
            paidQuery = paidQuery.Where(item => item.StoreId == storeId.Value);
        }

        var totalPaidAmount = await paidQuery
            .Select(item => item.Amount)
            .DefaultIfEmpty(0m)
            .SumAsync(cancellationToken);

        // 2. 聚合退款成功金额。
        var refundQuery =
            from refund in context.PaymentRefundRecords.AsNoTracking()
            join order in context.Orders.AsNoTracking()
                on refund.OrderId equals order.Id
            where refund.TenantId == tenantId
                  && order.TenantId == tenantId
                  && refund.Status == PaymentRefundStatus.Succeeded
                  && (refund.CompletedAt ?? refund.RequestedAt) >= monthStartUtc
                  && (refund.CompletedAt ?? refund.RequestedAt) < monthEnd
            select new
            {
                order.StoreId,
                refund.Amount
            };

        if (dimension == FinanceCostDimension.Store && storeId.HasValue)
        {
            refundQuery = refundQuery.Where(item => item.StoreId == storeId.Value);
        }

        var totalRefundAmount = await refundQuery
            .Select(item => item.Amount)
            .DefaultIfEmpty(0m)
            .SumAsync(cancellationToken);

        return RoundAmount(totalPaidAmount - totalRefundAmount);
    }

    private async Task<int> GetPaidOrderCountByMonthAsync(
        long tenantId,
        FinanceCostDimension dimension,
        long? storeId,
        DateTime monthStartUtc,
        CancellationToken cancellationToken)
    {
        var monthEnd = monthStartUtc.AddMonths(1);
        var paidOrderQuery =
            from payment in context.PaymentRecords.AsNoTracking()
            join order in context.Orders.AsNoTracking()
                on payment.OrderId equals order.Id
            where payment.TenantId == tenantId
                  && order.TenantId == tenantId
                  && payment.Status == PaymentStatus.Paid
                  && (payment.PaidAt ?? payment.CreatedAt) >= monthStartUtc
                  && (payment.PaidAt ?? payment.CreatedAt) < monthEnd
            select new
            {
                order.StoreId,
                payment.OrderId
            };

        if (dimension == FinanceCostDimension.Store && storeId.HasValue)
        {
            paidOrderQuery = paidOrderQuery.Where(item => item.StoreId == storeId.Value);
        }

        return await paidOrderQuery
            .Select(item => item.OrderId)
            .Distinct()
            .CountAsync(cancellationToken);
    }

    private static IReadOnlyList<FinanceCostCategorySnapshot> NormalizeCategoriesForSave(
        IReadOnlyList<FinanceCostCategorySnapshot> categories)
    {
        var source = categories ?? [];
        var map = source
            .GroupBy(item => item.Category)
            .ToDictionary(group => group.Key, group => group.First());

        return CategoryOrder.Select((category, index) =>
        {
            if (!map.TryGetValue(category, out var current))
            {
                return new FinanceCostCategorySnapshot
                {
                    Category = category,
                    TotalAmount = 0m,
                    Items = []
                };
            }

            var normalizedItems = (current.Items ?? [])
                .Where(item => !string.IsNullOrWhiteSpace(item.ItemName))
                .Select((item, itemIndex) => new FinanceCostDetailItemSnapshot
                {
                    ItemId = item.ItemId,
                    ItemName = item.ItemName.Trim(),
                    Amount = RoundAmount(item.Amount),
                    Quantity = item.Quantity.HasValue ? RoundAmount(item.Quantity.Value) : null,
                    UnitPrice = item.UnitPrice.HasValue ? RoundAmount(item.UnitPrice.Value) : null,
                    SortOrder = item.SortOrder <= 0 ? itemIndex + 1 : item.SortOrder
                })
                .OrderBy(item => item.SortOrder)
                .ThenBy(item => item.ItemName)
                .ToList();

            var totalAmount = current.TotalAmount > 0
                ? RoundAmount(current.TotalAmount)
                : RoundAmount(normalizedItems.Sum(item => item.Amount));

            return new FinanceCostCategorySnapshot
            {
                Category = category,
                TotalAmount = totalAmount,
                Items = normalizedItems
            };
        }).ToList();
    }

    private static DateTime NormalizeMonthStart(DateTime value)
    {
        var utcValue = value.Kind switch
        {
            DateTimeKind.Utc => value,
            DateTimeKind.Local => value.ToUniversalTime(),
            _ => DateTime.SpecifyKind(value, DateTimeKind.Utc)
        };

        return new DateTime(utcValue.Year, utcValue.Month, 1, 0, 0, 0, DateTimeKind.Utc);
    }

    private static long? NormalizeStoreId(FinanceCostDimension dimension, long? storeId)
    {
        if (dimension == FinanceCostDimension.Tenant)
        {
            return null;
        }

        return storeId.HasValue && storeId.Value > 0
            ? storeId.Value
            : null;
    }

    private static decimal RoundAmount(decimal value)
    {
        return decimal.Round(value, 2, MidpointRounding.AwayFromZero);
    }

    private static decimal CalculateMonthOnMonthChangeRate(decimal currentValue, decimal previousValue)
    {
        if (previousValue <= 0)
        {
            return currentValue <= 0 ? 0m : 100m;
        }

        var rate = (currentValue - previousValue) / previousValue * 100m;
        return RoundAmount(rate);
    }

    private static List<DateTime> BuildTrendMonths(DateTime currentMonth, int trendMonthCount)
    {
        var startMonth = currentMonth.AddMonths(0 - Math.Max(1, trendMonthCount) + 1);
        var result = new List<DateTime>(trendMonthCount);
        for (var index = 0; index < trendMonthCount; index++)
        {
            result.Add(startMonth.AddMonths(index));
        }

        return result;
    }
}
