using JetBrains.Annotations;

namespace Service;

public interface IBudgetService
{
    Task<List<BudgetResult>> GetListAsync(DateTime referenceDate);

    Task<string> ArchiveBudgetsAsync(int year, int month);

    /// <summary>
    /// 获取指定分类的统计信息（月度和年度）
    /// </summary>
    Task<BudgetCategoryStats> GetCategoryStatsAsync(BudgetCategory category, DateTime referenceDate);

    /// <summary>
    /// 获取未被预算覆盖的分类统计信息
    /// </summary>
    Task<List<UncoveredCategoryDetail>> GetUncoveredCategoriesAsync(BudgetCategory category, DateTime? referenceDate = null);

    Task<string?> GetArchiveSummaryAsync(int year, int month);

    /// <summary>
    /// 获取指定周期的存款预算信息
    /// </summary>
    Task<BudgetResult?> GetSavingsBudgetAsync(int year, int month, BudgetPeriodType type);
}

[UsedImplicitly]
public class BudgetService(
    IBudgetRepository budgetRepository,
    IBudgetArchiveRepository budgetArchiveRepository,
    ITransactionRecordRepository transactionRecordRepository,
    IOpenAiService openAiService,
    IConfigService configService,
    IMessageService messageService,
    ILogger<BudgetService> logger
) : IBudgetService
{
    public async Task<List<BudgetResult>> GetListAsync(DateTime referenceDate)
    {
        var year = referenceDate.Year;
        var month = referenceDate.Month;

        var isArchive = year < DateTime.Now.Year
                     || (year == DateTime.Now.Year && month < DateTime.Now.Month);

        if (isArchive)
        {
            var archive = await budgetArchiveRepository.GetArchiveAsync(year, month);

            if (archive != null)
            {
                var (start, end) = GetPeriodRange(DateTime.Now, BudgetPeriodType.Month, referenceDate);
                return [.. archive.Content.Select(c => new BudgetResult
                {
                    Id = c.Id,
                    Name = c.Name,
                    Type = c.Type,
                    Limit = c.Limit,
                    Current = c.Actual,
                    Category = c.Category,
                    SelectedCategories = c.SelectedCategories,
                    NoLimit = c.NoLimit,
                    IsMandatoryExpense = c.IsMandatoryExpense,
                    Description = c.Description,
                    PeriodStart = start,
                    PeriodEnd = end,
                })];
            }

            logger.LogWarning("获取预算列表时发现归档数据缺失，Year: {Year}, Month: {Month}", year, month);
        }

        var budgets = await budgetRepository.GetAllAsync();
        var dtos = new List<BudgetResult?>();

        foreach (var budget in budgets)
        {
            var currentAmount = await CalculateCurrentAmountAsync(budget, referenceDate);
            dtos.Add(BudgetResult.FromEntity(budget, currentAmount, referenceDate));
        }

        // 创造虚拟的存款预算
        dtos.Add(await GetSavingsDtoAsync(
            BudgetPeriodType.Month,
            referenceDate,
            budgets));
        dtos.Add(await GetSavingsDtoAsync(
            BudgetPeriodType.Year,
            referenceDate,
            budgets));

        dtos = dtos
            .Where(x => x != null)
            .Cast<BudgetResult>()
            .OrderByDescending(x => x.IsMandatoryExpense)
            .ThenBy(x => x.Type)
            .ThenByDescending(x => x.Current)
            .ToList()!;

        return [.. dtos.Where(dto => dto != null).Cast<BudgetResult>()];
    }

    public async Task<BudgetResult?> GetSavingsBudgetAsync(int year, int month, BudgetPeriodType type)
    {
        var referenceDate = new DateTime(year, month, 1);
        return await GetSavingsDtoAsync(type, referenceDate);
    }

    public async Task<BudgetCategoryStats> GetCategoryStatsAsync(BudgetCategory category, DateTime referenceDate)
    {
        var budgets = await GetListAsync(referenceDate);

        var result = new BudgetCategoryStats();

        // 获取月度统计
        result.Month = await CalculateCategoryStatsAsync(budgets, category, BudgetPeriodType.Month, referenceDate);

        // 获取年度统计
        result.Year = await CalculateCategoryStatsAsync(budgets, category, BudgetPeriodType.Year, referenceDate);

        return result;
    }

    public async Task<List<UncoveredCategoryDetail>> GetUncoveredCategoriesAsync(BudgetCategory category, DateTime? referenceDate = null)
    {
        var date = referenceDate ?? DateTime.Now;
        var transactionType = category switch
        {
            BudgetCategory.Expense => TransactionType.Expense,
            BudgetCategory.Income => TransactionType.Income,
            _ => TransactionType.None
        };

        if (transactionType == TransactionType.None) return [];

        // 1. 获取所有预算
        var budgets = (await budgetRepository.GetAllAsync()).ToList();
        var coveredCategories = budgets
            .Where(b => b.Category == category)
            .SelectMany(b => b.SelectedCategories.Split(',', StringSplitOptions.RemoveEmptyEntries))
            .ToHashSet();

        // 2. 获取分类统计
        var stats = await transactionRecordRepository.GetCategoryStatisticsAsync(date.Year, date.Month, transactionType);

        // 3. 过滤未覆盖的
        return stats
            .Where(s => !coveredCategories.Contains(s.Classify))
            .Select(s => new UncoveredCategoryDetail
            {
                Category = s.Classify,
                TransactionCount = s.Count,
                TotalAmount = s.Amount
            })
            .OrderByDescending(x => x.TotalAmount)
            .ToList();
    }

    public async Task<string?> GetArchiveSummaryAsync(int year, int month)
    {
        var archive = await budgetArchiveRepository.GetArchiveAsync(year, month);
        return archive?.Summary;
    }

    private async Task<BudgetStatsDto> CalculateCategoryStatsAsync(
        List<BudgetResult> budgets,
        BudgetCategory category,
        BudgetPeriodType statType,
        DateTime referenceDate)
    {
        var result = new BudgetStatsDto
        {
            PeriodType = statType,
            Rate = 0,
            Current = 0,
            Limit = 0,
            Count = 0
        };

        // 获取当前分类下所有预算，排除不记额预算
        var relevant = budgets
            .Where(b => b.Category == category && !b.NoLimit)
            .ToList();

        if (relevant.Count == 0)
        {
            return result;
        }

        result.Count = relevant.Count;
        decimal totalCurrent = 0;
        decimal totalLimit = 0;

        foreach (var budget in relevant)
        {
            // 限额折算
            var itemLimit = budget.Limit;
            if (statType == BudgetPeriodType.Month && budget.Type == BudgetPeriodType.Year)
            {
                // 月度视图下，年度预算不参与限额计算
                itemLimit = 0;
            }
            else if (statType == BudgetPeriodType.Year && budget.Type == BudgetPeriodType.Month)
            {
                // 年度视图下，月度预算折算为年度
                itemLimit = budget.Limit * 12;
            }
            totalLimit += itemLimit;

            // 当前值累加
            var selectedCategories = string.Join(',', budget.SelectedCategories);
            var currentAmount = await CalculateCurrentAmountAsync(new()
            {
                Name = budget.Name,
                Type = budget.Type,
                Limit = budget.Limit,
                Category = budget.Category,
                SelectedCategories = selectedCategories,
                StartDate = new DateTime(referenceDate.Year, referenceDate.Month, 1),
                IsMandatoryExpense = budget.IsMandatoryExpense
            }, referenceDate);
            if (budget.Type == statType)
            {
                totalCurrent += currentAmount;
            }
            else
            {
                // 如果周期不匹配
                if (statType == BudgetPeriodType.Year && budget.Type == BudgetPeriodType.Month)
                {
                    // 在年度视图下，月度预算计入其当前值（作为对年度目前的贡献）
                    totalCurrent += currentAmount;
                }
                // 月度视图下，年度预算的 current 不计入
            }
        }

        result.Limit = totalLimit;
        result.Current = totalCurrent;
        result.Rate = totalLimit > 0 ? totalCurrent / totalLimit * 100 : 0;

        // 计算每日/每月趋势
        var transactionType = category switch
        {
            BudgetCategory.Expense => TransactionType.Expense,
            BudgetCategory.Income => TransactionType.Income,
            _ => TransactionType.None
        };

        if (transactionType != TransactionType.None)
        {
            var hasGlobalBudget = relevant.Any(b => b.SelectedCategories.Length == 0);

            var allClassifies = hasGlobalBudget
                ? []
                : relevant
                    .SelectMany(b => b.SelectedCategories)
                    .Distinct()
                    .ToList();

            DateTime startDate, endDate;
            bool groupByMonth;

            if (statType == BudgetPeriodType.Month)
            {
                startDate = new DateTime(referenceDate.Year, referenceDate.Month, 1);
                endDate = startDate.AddMonths(1).AddDays(-1);
                groupByMonth = false;
            }
            else // Year
            {
                startDate = new DateTime(referenceDate.Year, 1, 1);
                endDate = startDate.AddYears(1).AddDays(-1);
                groupByMonth = true;
            }

            var dailyStats = await transactionRecordRepository.GetFilteredTrendStatisticsAsync(
                startDate,
                endDate,
                transactionType,
                allClassifies,
                groupByMonth);

            decimal accumulated = 0;
            var now = DateTime.Now;

            if (statType == BudgetPeriodType.Month)
            {
                var daysInMonth = DateTime.DaysInMonth(startDate.Year, startDate.Month);
                for (int i = 1; i <= daysInMonth; i++)
                {
                    var currentDate = new DateTime(startDate.Year, startDate.Month, i);
                    if (currentDate.Date > now.Date)
                    {
                        result.Trend.Add(null);
                        continue;
                    }

                    if (dailyStats.TryGetValue(currentDate.Date, out var amount))
                    {
                        accumulated += amount;
                    }
                    result.Trend.Add(accumulated);
                }
            }
            else // Year
            {
                for (int i = 1; i <= 12; i++)
                {
                    var currentMonthDate = new DateTime(startDate.Year, i, 1);

                    if (currentMonthDate.Year > now.Year || (currentMonthDate.Year == now.Year && i > now.Month))
                    {
                        result.Trend.Add(null);
                        continue;
                    }

                    if (dailyStats.TryGetValue(currentMonthDate, out var amount))
                    {
                        accumulated += amount;
                    }
                    result.Trend.Add(accumulated);
                }
            }
        }

        return result;
    }

    public async Task<string> ArchiveBudgetsAsync(int year, int month)
    {
        var referenceDate = new DateTime(year, month, 1);

        var budgets = await GetListAsync(referenceDate);

        var expenseSurplus = budgets
            .Where(b => b.Category == BudgetCategory.Expense && !b.NoLimit && b.Type == BudgetPeriodType.Month)
            .Sum(b => b.Limit - b.Current);

        var incomeSurplus = budgets
            .Where(b => b.Category == BudgetCategory.Income && !b.NoLimit && b.Type == BudgetPeriodType.Month)
            .Sum(b => b.Current - b.Limit);

        var content = budgets.Select(b => new BudgetArchiveContent
        {
            Id = b.Id,
            Name = b.Name,
            Type = b.Type,
            Limit = b.Limit,
            Actual = b.Current,
            Category = b.Category,
            SelectedCategories = b.SelectedCategories,
            NoLimit = b.NoLimit,
            IsMandatoryExpense = b.IsMandatoryExpense,
            Description = b.Description
        }).ToArray();

        var archive = await budgetArchiveRepository.GetArchiveAsync(year, month);

        if (archive != null)
        {
            archive.Content = content;
            archive.ArchiveDate = DateTime.Now;
            archive.ExpenseSurplus = expenseSurplus;
            archive.IncomeSurplus = incomeSurplus;
            if (!await budgetArchiveRepository.UpdateAsync(archive))
            {
                return "更新预算归档失败";
            }
        }
        else
        {
            archive = new BudgetArchive
            {
                Year = year,
                Month = month,
                Content = content,
                ArchiveDate = DateTime.Now,
                ExpenseSurplus = expenseSurplus,
                IncomeSurplus = incomeSurplus
            };

            if (!await budgetArchiveRepository.AddAsync(archive))
            {
                return "保存预算归档失败";
            }
        }

        _ = NotifyAsync(year, month);

        return string.Empty;
    }

    private async Task NotifyAsync(int year, int month)
    {
        try
        {
            var archives = await budgetArchiveRepository.GetListAsync(year, month);

            var archiveData = archives.SelectMany(a => a.Content.Select(c => new
            {
                c.Name,
                Type = c.Type.ToString(),
                c.Limit,
                c.Actual,
                Category = c.Category.ToString(),
                c.SelectedCategories
            })).ToList();

            var yearTransactions = await transactionRecordRepository.ExecuteDynamicSqlAsync(
                $"""
                SELECT 
                  COUNT(*) AS TransactionCount, 
                  SUM(ABS(Amount)) AS TotalAmount, 
                  Type, 
                  Classify
                FROM TransactionRecord
                WHERE OccurredAt >= '{year}-01-01' 
                  AND OccurredAt < '{year + 1}-01-01'
                GROUP BY Type, Classify
                ORDER BY TotalAmount DESC
                """
            );
            var monthYear = new DateTime(year, month, 1).AddMonths(1);
            var monthTransactions = await transactionRecordRepository.ExecuteDynamicSqlAsync(
                $"""
                SELECT 
                  COUNT(*) AS TransactionCount, 
                  SUM(ABS(Amount)) AS TotalAmount, 
                  Type, 
                  Classify
                FROM TransactionRecord
                WHERE OccurredAt >= '{year}-{month:00}-01' 
                  AND OccurredAt < '{monthYear:yyyy-MM-dd}'
                GROUP BY Type, Classify
                ORDER BY TotalAmount DESC
                """
            );

            // 分析未被预算覆盖的分类 (仅针对支出类型 Type=0)
            var budgetedCategories = archiveData
                .SelectMany(b => b.SelectedCategories)
                .Where(c => !string.IsNullOrEmpty(c))
                .Distinct()
                .ToHashSet();

            var uncovered = monthTransactions
                .Where(t =>
                {
                    var dict = (IDictionary<string, object>)t;
                    var classify = dict["Classify"].ToString() ?? "";
                    var type = Convert.ToInt32(dict["Type"]);
                    return type == 0 && !budgetedCategories.Contains(classify);
                })
                .ToList();

            logger.LogInformation("预算执行数据{JSON}", JsonSerializer.Serialize(archiveData));
            logger.LogInformation("本月消费明细{JSON}", JsonSerializer.Serialize(monthTransactions));
            logger.LogInformation("全年累计消费概况{JSON}", JsonSerializer.Serialize(yearTransactions));
            logger.LogInformation("未被预算覆盖的分类{JSON}", JsonSerializer.Serialize(uncovered));

            var dataPrompt = $"""
            报告周期：{year}年{month}月

            1. 预算执行数据（JSON）：
            {JsonSerializer.Serialize(archiveData)}
            
            2. 本月账单类目明细（按分类, JSON）：
            {JsonSerializer.Serialize(monthTransactions)}
            
            3. 全年累计账单类目明细（按分类, JSON）：
            {JsonSerializer.Serialize(yearTransactions)}

            4. 未被任何预算覆盖的支出分类（JSON）：
            {JsonSerializer.Serialize(uncovered)}

            请生成一份专业且美观的预算执行分析报告，严格遵守以下要求：
            
            【内容要求】
            1. 概览：总结本月预算达成情况。
            2. 预算详情：使用 HTML 表格展示预算执行明细（预算项、预算额、实际额、使用/达成率、状态）。
            3. 超支/异常预警：重点分析超支项或支出异常的分类。
            4. 消费透视：针对“未被预算覆盖的支出”提供分析建议。分析这些账单产生的合理性，并评估是否需要为其中的大额或频发分类建立新预算。
            5. 改进建议：根据当前时间进度和预算完成进度，基于本月整体收入支出情况，给出下月预算调整或消费改进的专业化建议。
            6. 语言风格：专业、清晰、简洁，适合财务报告阅读。
            7. 如果报告月份是12月，需要报告年度预算的执行情况。
            
            【格式要求】
            1. 使用HTML格式（移动端H5页面风格）
            2. 生成清晰的报告标题（基于用户问题）
            3. 使用表格展示统计数据（table > thead/tbody > tr > th/td），
                3.1 table要求不能超过屏幕宽度，尽可能简洁明了，避免冗余信息
                3.2 预算金额精确到整数即可，实际金额精确到小数点后1位
            4. 使用合适的HTML标签：h2（标题）、h3（小节）、p（段落）、table（表格）、ul/li（列表）、strong（强调）
            5. 支出金额用 <span class='expense-value'>金额</span> 包裹
            6. 收入金额用 <span class='income-value'>金额</span> 包裹
            7. 重要结论用 <span class='highlight'>内容</span> 高亮
            
            【样式限制（重要）】
            8. 不要包含 html、body、head 标签
            9. 不要使用任何 style 属性或 <style> 标签
            10. 不要设置 background、background-color、color 等样式属性
            11. 不要使用 div 包裹大段内容

            【系统信息】
            当前时间：{DateTime.Now:yyyy-MM-dd HH:mm:ss}
            预算归档周期：{year}年{month}月
            
            直接输出纯净 HTML 内容，不要带有 Markdown 代码块包裹。
            """;

            var htmlReport = await openAiService.ChatAsync(dataPrompt);
            if (!string.IsNullOrEmpty(htmlReport))
            {
                await messageService.AddAsync(
                    title: $"{year}年{month}月 - 预算归档报告",
                    content: htmlReport,
                    type: MessageType.Html,
                    url: "/balance?tab=message");

                // 同时保存到归档总结
                var first = archives.First();
                first.Summary = htmlReport;
                await budgetArchiveRepository.UpdateAsync(first);
            }
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "生成预算执行通知报告失败");
        }
    }

    private async Task<decimal> CalculateCurrentAmountAsync(BudgetRecord budget, DateTime? now = null)
    {
        var referenceDate = now ?? DateTime.Now;
        var (startDate, endDate) = GetPeriodRange(budget.StartDate, budget.Type, referenceDate);

        var actualAmount = await budgetRepository.GetCurrentAmountAsync(budget, startDate, endDate);

        // 如果是硬性消费，且是当前年当前月，则根据经过的天数累加
        if (actualAmount == 0
            && budget.IsMandatoryExpense
            && referenceDate.Year == startDate.Year
            && referenceDate.Month == startDate.Month)
        {
            if (budget.Type == BudgetPeriodType.Month)
            {
                // 计算本月的天数
                var daysInMonth = DateTime.DaysInMonth(referenceDate.Year, referenceDate.Month);
                // 计算当前已经过的天数（包括今天）
                var daysElapsed = referenceDate.Day;
                // 根据预算金额和经过天数计算应累加的金额
                var mandatoryAccumulation = budget.Limit * daysElapsed / daysInMonth;
                // 返回实际消费和硬性消费累加中的较大值
                return mandatoryAccumulation;
            }

            if (budget.Type == BudgetPeriodType.Year)
            {
                // 计算本年的天数（考虑闰年）
                var daysInYear = DateTime.IsLeapYear(referenceDate.Year) ? 366 : 365;
                // 计算当前已经过的天数（包括今天）
                var daysElapsed = referenceDate.DayOfYear;
                // 根据预算金额和经过天数计算应累加的金额
                var mandatoryAccumulation = budget.Limit * daysElapsed / daysInYear;
                // 返回实际消费和硬性消费累加中的较大值
                return mandatoryAccumulation;
            }

        }

        return actualAmount;
    }

    internal static (DateTime start, DateTime end) GetPeriodRange(DateTime startDate, BudgetPeriodType type, DateTime referenceDate)
    {
        DateTime start;
        DateTime end;

        if (type == BudgetPeriodType.Month)
        {
            start = new DateTime(referenceDate.Year, referenceDate.Month, 1);
            end = start.AddMonths(1).AddDays(-1).AddHours(23).AddMinutes(59).AddSeconds(59);
        }
        else if (type == BudgetPeriodType.Year)
        {
            start = new DateTime(referenceDate.Year, 1, 1);
            end = new DateTime(referenceDate.Year, 12, 31).AddHours(23).AddMinutes(59).AddSeconds(59);
        }
        else
        {
            start = startDate;
            end = DateTime.MaxValue;
        }

        return (start, end);
    }

    private async Task<BudgetResult?> GetSavingsDtoAsync(
        BudgetPeriodType periodType,
        DateTime? referenceDate = null,
        IEnumerable<BudgetRecord>? existingBudgets = null)
    {
        var allBudgets = existingBudgets;

        if (existingBudgets == null)
        {
            allBudgets = await budgetRepository.GetAllAsync();
        }

        if (allBudgets == null)
        {
            return null;
        }

        allBudgets = allBudgets
        // 排序顺序 1.硬性预算 2.月度->年度 3.实际金额倒叙
            .OrderBy(b => b.IsMandatoryExpense)
            .ThenBy(b => b.Type)
            .ThenByDescending(b => b.Limit);

        var date = referenceDate ?? DateTime.Now;

        decimal incomeLimitAtPeriod = 0;
        decimal expenseLimitAtPeriod = 0;
        decimal noLimitIncomeAtPeriod = 0;    // 新增：不记额收入汇总
        decimal noLimitExpenseAtPeriod = 0;   // 新增：不记额支出汇总

        var incomeItems = new List<(string Name, decimal Limit, decimal Factor, decimal Historical, decimal Total)>();
        var expenseItems = new List<(string Name, decimal Limit, decimal Factor, decimal Historical, decimal Total)>();
        var noLimitIncomeItems = new List<(string Name, decimal Amount)>();  // 新增
        var noLimitExpenseItems = new List<(string Name, decimal Amount)>();  // 新增

        // 如果是年度计算，先从归档中获取所有历史数据
        Dictionary<(long Id, int Month), (decimal HistoricalLimit, BudgetCategory Category, string Name)> historicalData = new();
        
        if (periodType == BudgetPeriodType.Year)
        {
            var yearArchives = await budgetArchiveRepository.GetArchivesByYearAsync(date.Year);

            // 按预算ID和月份记录历史数据
            foreach (var archive in yearArchives)
            {
                foreach (var content in archive.Content)
                {
                    // 跳过存款类预算
                    if (content.Category == BudgetCategory.Savings) continue;

                    historicalData[(content.Id, archive.Month)] = (
                        content.Limit,
                        content.Category,
                        content.Name);
                }
            }
        }

        // 处理当前预算
        var processedIds = new HashSet<long>();
        foreach (var b in allBudgets)
        {
            if (b.Category == BudgetCategory.Savings) continue;

            processedIds.Add(b.Id);
            decimal factor;
            decimal historicalAmount = 0m;
            var historicalMonths = new List<int>();

            if (periodType == BudgetPeriodType.Year)
            {
                if (b.Type == BudgetPeriodType.Month)
                {
                    // 月度预算在年度计算时：历史归档 + 剩余月份预算
                    // 收集该预算的所有历史月份数据
                    foreach (var ((id, month), (limit, _, _)) in historicalData)
                    {
                        if (id == b.Id)
                        {
                            historicalAmount += limit;
                            historicalMonths.Add(month);
                        }
                    }

                    // 计算剩余月份数（当前月到12月）
                    var remainingMonths = 12 - date.Month + 1;
                    factor = remainingMonths;
                }
                else if (b.Type == BudgetPeriodType.Year)
                {
                    factor = 1;
                }
                else
                {
                    factor = 0;
                }
            }
            else if (periodType == BudgetPeriodType.Month)
            {
                factor = b.Type switch
                {
                    BudgetPeriodType.Month => 1,
                    BudgetPeriodType.Year => 0,
                    _ => 0
                };
            }
            else
            {
                factor = 0; // 其他周期暂不计算虚拟存款
            }

            if (factor <= 0 && historicalAmount <= 0) continue;

            // 处理不记额预算
            if (b.NoLimit)
            {
                var actualAmount = await CalculateCurrentAmountAsync(b, date);
                if (b.Category == BudgetCategory.Income)
                {
                    noLimitIncomeAtPeriod += actualAmount;
                    noLimitIncomeItems.Add((b.Name, actualAmount));
                }
                else if (b.Category == BudgetCategory.Expense)
                {
                    noLimitExpenseAtPeriod += actualAmount;
                    noLimitExpenseItems.Add((b.Name, actualAmount));
                }
            }
            else
            {
                // 普通预算：历史金额 + 当前预算折算
                var subtotal = historicalAmount + b.Limit * factor;
                var displayName = b.Name;
                
                // 如果有历史月份，添加月份范围显示
                if (historicalMonths.Count > 0)
                {
                    historicalMonths.Sort();
                    var monthRange = historicalMonths.Count == 1 
                        ? $"{historicalMonths[0]}月" 
                        : $"{historicalMonths[0]}~{historicalMonths[^1]}月";
                    displayName = $"{b.Name} ({monthRange})";
                }
                
                if (b.Category == BudgetCategory.Income)
                {
                    incomeLimitAtPeriod += subtotal;
                    incomeItems.Add((displayName, b.Limit, factor, historicalAmount, subtotal));
                }
                else if (b.Category == BudgetCategory.Expense)
                {
                    expenseLimitAtPeriod += subtotal;
                    expenseItems.Add((displayName, b.Limit, factor, historicalAmount, subtotal));
                }
            }
        }

        // 处理已删除的预算（只在归档中存在，但当前预算列表中不存在的）
        if (periodType == BudgetPeriodType.Year)
        {
            // 按预算ID分组
            var deletedBudgets = historicalData
                .Where(kvp => !processedIds.Contains(kvp.Key.Id))
                .GroupBy(kvp => kvp.Key.Id);

            foreach (var group in deletedBudgets)
            {
                var months = group.Select(g => g.Key.Month).OrderBy(m => m).ToList();
                var totalLimit = group.Sum(g => g.Value.HistoricalLimit);
                var (_, category, name) = group.First().Value;

                var monthRange = months.Count == 1 
                    ? $"{months[0]}月" 
                    : $"{months[0]}~{months[^1]}月";
                var displayName = $"{name} ({monthRange}, 已删除)";

                // 这是一个已被删除的预算，但有历史数据
                if (category == BudgetCategory.Income)
                {
                    incomeLimitAtPeriod += totalLimit;
                    incomeItems.Add((displayName, 0, months.Count, totalLimit, totalLimit));
                }
                else if (category == BudgetCategory.Expense)
                {
                    expenseLimitAtPeriod += totalLimit;
                    expenseItems.Add((displayName, 0, months.Count, totalLimit, totalLimit));
                }
            }
        }

        var description = new StringBuilder();
        description.Append("<h3>预算收入明细</h3>");
        if (incomeItems.Count == 0) description.Append("<p>无收入预算</p>");
        else
        {
            // 根据是否有历史数据决定表格列
            var hasHistoricalData = incomeItems.Any(i => i.Historical > 0);

            if (hasHistoricalData)
            {
                description.Append("""
                <table>
                    <thead>
                        <tr>
                            <th>名称</th>
                            <th>当前预算</th>
                            <th>剩余月数</th>
                            <th>历史归档</th>
                            <th>合计</th>
                        </tr>
                    </thead>
                <tbody>
                """);
                foreach (var item in incomeItems)
                {
                    description.Append($"""
                    <tr>
                        <td>{item.Name}</td>
                        <td>{item.Limit:N0}</td>
                        <td>{item.Factor:0.##}</td>
                        <td>{item.Historical:N0}</td>
                        <td><span class='income-value'>{item.Total:N0}</span></td>
                    </tr>
                    """);
                }
            }
            else
            {
                description.Append("""
                <table>
                    <thead>
                        <tr>
                            <th>名称</th>
                            <th>金额</th>
                            <th>折算</th>
                            <th>合计</th>
                        </tr>
                    </thead>
                <tbody>
                """);
                foreach (var item in incomeItems)
                {
                    description.Append($"""
                    <tr>
                        <td>{item.Name}</td>
                        <td>{item.Limit:N0}</td>
                        <td>{item.Factor:0.##}</td>
                        <td><span class='income-value'>{item.Total:N0}</span></td>
                    </tr>
                    """);
                }
            }
            description.Append("</tbody></table>");
        }
        description.Append($"<p>收入合计: <span class='income-value'><strong>{incomeLimitAtPeriod:N0}</strong></span></p>");

        if (periodType == BudgetPeriodType.Year && noLimitIncomeItems.Count > 0)
        {
            description.Append("<h3>不记额收入明细</h3>");
            description.Append("""
            <table>
                <thead>
                    <tr>
                        <th>预算名称</th>
                        <th>实际发生</th>
                    </tr>
                </thead>
            <tbody>
            """);
            foreach (var (name, amount) in noLimitIncomeItems)
            {
                description.Append($"""
                <tr>
                    <td>{name}</td>
                    <td><span class='income-value'>{amount:N0}</span></td>
                </tr>
                """);
            }
            description.Append("</tbody></table>");
            description.Append($"<p>不记额收入合计: <span class='income-value'><strong>{noLimitIncomeAtPeriod:N0}</strong></span></p>");
        }

        description.Append("<h3>预算支出明细</h3>");
        if (expenseItems.Count == 0) description.Append("<p>无支出预算</p>");
        else
        {
            // 根据是否有历史数据决定表格列
            var hasHistoricalData = expenseItems.Any(i => i.Historical > 0);

            if (hasHistoricalData)
            {
                description.Append("""
                <table>
                    <thead>
                        <tr>
                            <th>名称</th>
                            <th>当前预算</th>
                            <th>剩余月数</th>
                            <th>历史归档</th>
                            <th>合计</th>
                        </tr>
                    </thead>
                <tbody>
                """);
                foreach (var item in expenseItems)
                {
                    description.Append($"""
                    <tr>
                        <td>{item.Name}</td>
                        <td>{item.Limit:N0}</td>
                        <td>{item.Factor:0.##}</td>
                        <td>{item.Historical:N0}</td>
                        <td><span class='expense-value'>{item.Total:N0}</span></td>
                    </tr>
                    """);
                }
            }
            else
            {
                description.Append("""
                <table>
                    <thead>
                        <tr>
                            <th>名称</th>
                            <th>金额</th>
                            <th>折算</th>
                            <th>合计</th>
                        </tr>
                    </thead>
                <tbody>
                """);
                foreach (var item in expenseItems)
                {
                    description.Append($"""
                    <tr>
                        <td>{item.Name}</td>
                        <td>{item.Limit:N0}</td>
                        <td>{item.Factor:0.##}</td>
                        <td><span class='expense-value'>{item.Total:N0}</span></td>
                    </tr>
                    """);
                }
            }
            description.Append("</tbody></table>");
        }
        description.Append($"<p>支出合计: <span class='expense-value'><strong>{expenseLimitAtPeriod:N0}</strong></span></p>");

        if (periodType == BudgetPeriodType.Year && noLimitExpenseItems.Count > 0)
        {
            description.Append("<h3>不记额支出明细</h3>");
            description.Append("""
            <table>
                <thead>
                    <tr>
                        <th>预算名称</th>
                        <th>实际发生</th>
                    </tr>
                </thead>
            <tbody>
            """);
            foreach (var (name, amount) in noLimitExpenseItems)
            {
                description.Append($"""
                <tr>
                    <td>{name}</td>
                    <td><span class='expense-value'>{amount:N0}</span></td>
                </tr>
                """);
            }
            description.Append("</tbody></table>");
            description.Append($"<p>不记额支出合计: <span class='expense-value'><strong>{noLimitExpenseAtPeriod:N0}</strong></span></p>");
        }

        description.Append("<h3>存款计划结论</h3>");
        // 修改计算公式：包含不记额收入和支出
        var totalIncome = incomeLimitAtPeriod + noLimitIncomeAtPeriod;
        var totalExpense = expenseLimitAtPeriod + noLimitExpenseAtPeriod;
        description.Append($"<p>计划收入 = 预算 <span class='income-value'>{incomeLimitAtPeriod:N0}</span> + 不记额 <span class='income-value'>{noLimitIncomeAtPeriod:N0}</span> = <span class='income-value'><strong>{totalIncome:N0}</strong></span></p>");
        description.Append($"<p>计划支出 = 预算 <span class='expense-value'>{expenseLimitAtPeriod:N0}</span> + 不记额 <span class='expense-value'>{noLimitExpenseAtPeriod:N0}</span> = <span class='expense-value'><strong>{totalExpense:N0}</strong></span></p>");
        description.Append($"<p>计划盈余 = 计划收入 <span class='income-value'>{totalIncome:N0}</span> - 计划支出 <span class='expense-value'>{totalExpense:N0}</span> = <span class='income-value'><strong>{totalIncome - totalExpense:N0}</strong></span></p>");

        decimal historicalSurplus = 0;
        if (periodType == BudgetPeriodType.Year)
        {
            var archives = await budgetArchiveRepository.GetArchivesByYearAsync(date.Year);
            if (archives.Count > 0)
            {
                var expenseSurplus = archives.Sum(a => a.ExpenseSurplus);
                var incomeSurplus = archives.Sum(a => a.IncomeSurplus);
                historicalSurplus = expenseSurplus + incomeSurplus;

                description.Append("<h3>历史月份盈亏</h3>");
                description.Append("""
                <table>
                    <thead>
                        <tr>
                            <th>月份</th>
                            <th>支出结余</th>
                            <th>收入结余</th>
                            <th>合计</th>
                        </tr>
                    </thead>
                <tbody>
                """);

                foreach (var archive in archives)
                {
                    var monthlyTotal = archive.ExpenseSurplus + archive.IncomeSurplus;
                    var monthlyClass = monthlyTotal >= 0 ? "income-value" : "expense-value";
                    description.Append($"""
                    <tr>
                        <td>{archive.Month}月</td>
                        <td><span class='{(archive.ExpenseSurplus >= 0 ? "income-value" : "expense-value")}'>{archive.ExpenseSurplus:N0}</span></td>
                        <td><span class='{(archive.IncomeSurplus >= 0 ? "income-value" : "expense-value")}'>{archive.IncomeSurplus:N0}</span></td>
                        <td><span class='{monthlyClass}'>{monthlyTotal:N0}</span></td>
                    </tr>
                    """);
                }

                var totalClass = historicalSurplus >= 0 ? "income-value" : "expense-value";
                description.Append($"""
                    <tr>
                        <td><strong>汇总</strong></td>
                        <td><span class='{(expenseSurplus >= 0 ? "income-value" : "expense-value")}'><strong>{expenseSurplus:N0}</strong></span></td>
                        <td><span class='{(incomeSurplus >= 0 ? "income-value" : "expense-value")}'><strong>{incomeSurplus:N0}</strong></span></td>
                        <td><span class='{totalClass}'><strong>{historicalSurplus:N0}</strong></span></td>
                    </tr>
                </tbody></table>
                """);
            }
            var finalGoal = totalIncome - totalExpense + historicalSurplus;
            description.Append($"""
                <p>
                    动态目标 = 计划盈余 <span class='{(totalIncome - totalExpense >= 0 ? "income-value" : "expense-value")}'>{totalIncome - totalExpense:N0}</span>
                    + 年度历史盈亏 <span class='{(historicalSurplus >= 0 ? "income-value" : "expense-value")}'>{historicalSurplus:N0}</span> 
                    = <span class='{(finalGoal >= 0 ? "income-value" : "expense-value")}'><strong>{finalGoal:N0}</strong></span></p>
                """);
        }

        var finalLimit = periodType == BudgetPeriodType.Year ? (totalIncome - totalExpense + historicalSurplus) : (totalIncome - totalExpense);

        var virtualBudget = await BuildVirtualSavingsBudgetRecordAsync(
            periodType == BudgetPeriodType.Year ? -1 : -2,
            date,
            finalLimit);

        // 计算实际发生的 收入 - 支出
        var current = await CalculateCurrentAmountAsync(new BudgetRecord
        {
            Category = virtualBudget.Category,
            Type = virtualBudget.Type,
            SelectedCategories = virtualBudget.SelectedCategories,
            StartDate = virtualBudget.StartDate,
        }, date);

        return BudgetResult.FromEntity(virtualBudget, current, date, description.ToString());
    }

    private async Task<BudgetRecord> BuildVirtualSavingsBudgetRecordAsync(
        long id,
        DateTime date,
        decimal limit)
    {
        var savingsCategories = await configService.GetConfigByKeyAsync<string>("SavingsCategories") ?? string.Empty;
        return new BudgetRecord
        {
            Id = id,
            Name = id == -1 ? "年度存款" : "月度存款",
            Category = BudgetCategory.Savings,
            Type = id == -1 ? BudgetPeriodType.Year : BudgetPeriodType.Month,
            Limit = limit,
            StartDate = id == -1
                ? new DateTime(date.Year, 1, 1)
                : new DateTime(date.Year, date.Month, 1),
            SelectedCategories = savingsCategories
        };
    }
}

public record BudgetResult
{
    public long Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public BudgetPeriodType Type { get; set; }
    public decimal Limit { get; set; }
    public decimal Current { get; set; }
    public BudgetCategory Category { get; set; }
    public string[] SelectedCategories { get; set; } = [];
    public string StartDate { get; set; } = string.Empty;
    public string Period { get; set; } = string.Empty;
    public DateTime? PeriodStart { get; set; }
    public DateTime? PeriodEnd { get; set; }
    public bool NoLimit { get; set; }
    public bool IsMandatoryExpense { get; set; }
    public string Description { get; set; } = string.Empty;

    public static BudgetResult FromEntity(
        BudgetRecord entity,
        decimal currentAmount = 0,
        DateTime? referenceDate = null,
        string description = "")
    {
        var date = referenceDate ?? DateTime.Now;
        var (start, end) = BudgetService.GetPeriodRange(entity.StartDate, entity.Type, date);

        return new BudgetResult
        {
            Id = entity.Id,
            Name = entity.Name,
            Type = entity.Type,
            Limit = entity.Limit,
            Current = currentAmount,
            Category = entity.Category,
            SelectedCategories = string.IsNullOrEmpty(entity.SelectedCategories)
                ? []
                : entity.SelectedCategories.Split(','),
            StartDate = entity.StartDate.ToString("yyyy-MM-dd"),
            Period = entity.Type switch
            {
                BudgetPeriodType.Year => $"{start:yy}年",
                BudgetPeriodType.Month => $"{start:yy}年第{start.Month}月",
                _ => $"{start:yyyy-MM-dd} ~ {end:yyyy-MM-dd}"
            },
            PeriodStart = start,
            PeriodEnd = end,
            NoLimit = entity.NoLimit,
            IsMandatoryExpense = entity.IsMandatoryExpense,
            Description = description
        };
    }
}
/// <summary>
/// 预算统计结果 DTO
/// </summary>
public class BudgetStatsDto
{
    /// <summary>
    /// 统计周期类型（Month/Year）
    /// </summary>
    public BudgetPeriodType PeriodType { get; set; }

    /// <summary>
    /// 使用率百分比（0-100）
    /// </summary>
    public decimal Rate { get; set; }

    /// <summary>
    /// 实际金额
    /// </summary>
    public decimal Current { get; set; }

    /// <summary>
    /// 目标/限额金额
    /// </summary>
    public decimal Limit { get; set; }

    /// <summary>
    /// 预算项数量
    /// </summary>
    public int Count { get; set; }

    /// <summary>
    /// 每日/每月累计金额趋势（对应当前周期内的实际发生额累计值）
    /// </summary>
    public List<decimal?> Trend { get; set; } = [];
}

/// <summary>
/// 分类统计结果
/// </summary>
public class BudgetCategoryStats
{
    /// <summary>
    /// 月度统计
    /// </summary>
    public BudgetStatsDto Month { get; set; } = new();

    /// <summary>
    /// 年度统计
    /// </summary>
    public BudgetStatsDto Year { get; set; } = new();
}

public class UncoveredCategoryDetail
{
    public string Category { get; set; } = string.Empty;
    public int TransactionCount { get; set; }
    public decimal TotalAmount { get; set; }
}
