using Service.Transaction;

namespace Service.Budget;

public interface IBudgetSavingsService
{
    Task<BudgetResult> GetSavingsDtoAsync(
        BudgetPeriodType periodType,
        DateTime? referenceDate = null,
        IEnumerable<BudgetRecord>? existingBudgets = null);
}

public class BudgetSavingsService(
    IBudgetRepository budgetRepository,
    IBudgetArchiveRepository budgetArchiveRepository,
    ITransactionStatisticsService transactionStatisticsService,
    IConfigService configService,
    IDateTimeProvider dateTimeProvider
) : IBudgetSavingsService
{
    public async Task<BudgetResult> GetSavingsDtoAsync(
        BudgetPeriodType periodType,
        DateTime? referenceDate = null,
        IEnumerable<BudgetRecord>? existingBudgets = null)
    {
        var budgets = existingBudgets;

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

        if (budgets == null)
        {
            throw new InvalidOperationException("No budgets found.");
        }

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

        var year = referenceDate?.Year ?? dateTimeProvider.Now.Year;
        var month = referenceDate?.Month ?? dateTimeProvider.Now.Month;

        if (periodType == BudgetPeriodType.Month)
        {
            return await GetForMonthAsync(budgets, year, month);
        }
        else if (periodType == BudgetPeriodType.Year)
        {
            return await GetForYearAsync(budgets, year);
        }

        throw new NotSupportedException($"Period type {periodType} is not supported.");
    }

    private async Task<BudgetResult> GetForMonthAsync(
        IEnumerable<BudgetRecord> budgets,
        int year,
        int month)
    {
        var transactionClassify = await transactionStatisticsService.GetAmountGroupByClassifyAsync(
            new DateTime(year, month, 1),
            new DateTime(year, month, 1).AddMonths(1)
        );

        var monthlyIncomeItems = new List<(string name, decimal limit, decimal current, bool isMandatory)>();
        var monthlyExpenseItems = new List<(string name, decimal limit, decimal current, bool isMandatory)>();
        var monthlyBudgets = budgets
            .Where(b => b.Type == BudgetPeriodType.Month);
        foreach (var budget in monthlyBudgets)
        {
            var classifyList = budget.SelectedCategories.Split(',', StringSplitOptions.RemoveEmptyEntries);

            decimal currentAmount = 0;
            var transactionType = budget.Category switch
            {
                BudgetCategory.Income => TransactionType.Income,
                BudgetCategory.Expense => TransactionType.Expense,
                _ => throw new NotSupportedException($"Budget category {budget.Category} is not supported.")
            };

            foreach (var classify in classifyList)
            {
                // 获取分类+收入支出类型一致的金额
                if (transactionClassify.TryGetValue((classify, transactionType), out var amount))
                {
                    currentAmount += amount;
                }
            }

            // 硬性预算 如果实际发生金额小于 应(总天数/实际天数)发生金额 
            // 直接取应发生金额(为了预算的准确性)
            if (budget.IsMandatoryExpense && currentAmount == 0)
            {
                currentAmount = budget.Limit / DateTime.DaysInMonth(year, month) * dateTimeProvider.Now.Day;
            }

            if (budget.Category == BudgetCategory.Income)
            {
                monthlyIncomeItems.Add((
                    name: budget.Name,
                    limit: budget.Limit,
                    current: currentAmount,
                    isMandatory: budget.IsMandatoryExpense
                ));
            }
            else if (budget.Category == BudgetCategory.Expense)
            {
                monthlyExpenseItems.Add((
                    name: budget.Name,
                    limit: budget.Limit,
                    current: currentAmount,
                    isMandatory: budget.IsMandatoryExpense
                ));
            }
        }

        var yearlyIncomeItems = new List<(string name, decimal limit, decimal current)>();
        var yearlyExpenseItems = new List<(string name, decimal limit, decimal current)>();
        var yearlyBudgets = budgets
            .Where(b => b.Type == BudgetPeriodType.Year);
        // 只需要考虑实际发生在本月的年度预算 因为他会影响到月度的结余情况
        foreach (var budget in yearlyBudgets)
        {
            var classifyList = budget.SelectedCategories.Split(',', StringSplitOptions.RemoveEmptyEntries);

            decimal currentAmount = 0;
            var transactionType = budget.Category switch
            {
                BudgetCategory.Income => TransactionType.Income,
                BudgetCategory.Expense => TransactionType.Expense,
                _ => throw new NotSupportedException($"Budget category {budget.Category} is not supported.")
            };

            foreach (var classify in classifyList)
            {
                // 获取分类+收入支出类型一致的金额
                if (transactionClassify.TryGetValue((classify, transactionType), out var amount))
                {
                    currentAmount += amount;
                }
            }

            if (currentAmount == 0)
            {
                continue;
            }

            if (budget.Category == BudgetCategory.Income)
            {
                yearlyIncomeItems.Add((
                    name: budget.Name,
                    limit: budget.Limit,
                    current: currentAmount
                ));
            }
            else if (budget.Category == BudgetCategory.Expense)
            {
                yearlyExpenseItems.Add((
                    name: budget.Name,
                    limit: budget.Limit,
                    current: currentAmount
                ));
            }
        }

        var description = new StringBuilder();

        #region 构建月度收入支出明细表格
        description.AppendLine("<h3>月度预算收入明细</h3>");
        description.AppendLine("""
        <table>
            <thead>
                <tr>
                    <th>名称</th>
                    <th>预算</th>
                    <th>硬性收入</th>
                </tr>
            </thead>
        <tbody>
        """);

        foreach (var item in monthlyIncomeItems)
        {
            description.AppendLine($"""
            <tr>
                <td>{item.name}</td>
                <td>{item.limit:N0}</td>
                <td>{(item.isMandatory ? "是" : "否")}</td>
            </tr>
            """);
        }

        description.AppendLine("</tbody></table>");
        description.AppendLine($"""
        <p>
            收入合计: 
            <span class='income-value'>
                <strong>{monthlyIncomeItems.Sum(item => item.limit):N0}</strong>
            </span>
        </p>
        """);

        description.AppendLine("<h3>月度预算支出明细</h3>");
        description.AppendLine("""
        <table>
            <thead>
                <tr>
                    <th>名称</th>
                    <th>预算</th>
                    <th>硬性支出</th>
                </tr>
            </thead>
        <tbody>
        """);
        foreach (var item in monthlyExpenseItems)
        {
            description.AppendLine($"""
            <tr>
                <td>{item.name}</td>
                <td>{item.limit:N0}</td>
                <td>{(item.isMandatory ? "是" : "否")}</td>
            </tr>
            """);
        }
        description.AppendLine("</tbody></table>");
        description.AppendLine($"""
        <p>
            支出合计: 
            <span class='expense-value'>
                <strong>{monthlyExpenseItems.Sum(item => item.limit):N0}</strong>
            </span>
        </p>
        """);
        #endregion

        #region 构建发生在本月的年度预算收入支出明细表格
        if (yearlyIncomeItems.Any())
        {
            description.AppendLine("<h3>年度收入预算（发生在本月）</h3>");
            description.AppendLine("""
        <table>
            <thead>
                <tr>
                    <th>名称</th>
                    <th>预算</th>
                    <th>本月收入</th>
                </tr>
            </thead>
        <tbody>
        """);

            foreach (var item in yearlyIncomeItems)
            {
                description.AppendLine($"""
            <tr>
                <td>{item.name}</td>
                <td>{(item.limit == 0 ? "不限额" : item.limit.ToString("N0"))}</td>
                <td><span class='income-value'>{item.current:N0}</span></td>
            </tr>
            """);
            }

            description.AppendLine("</tbody></table>");
            description.AppendLine($"""
        <p>
            收入合计: 
            <span class='income-value'>
                <strong>{yearlyIncomeItems.Sum(item => item.current):N0}</strong>
            </span>
        </p>
        """);
        }

        if (yearlyExpenseItems.Any())
        {
            description.AppendLine("<h3>年度支出预算（发生在本月）</h3>");
            description.AppendLine("""
        <table>
            <thead>
                <tr>
                    <th>名称</th>
                    <th>预算</th>
                    <th>本月支出</th>
                </tr>
            </thead>
        <tbody>
        """);
            foreach (var item in yearlyExpenseItems)
            {
                description.AppendLine($"""
            <tr>
                <td>{item.name}</td>
                <td>{(item.limit == 0 ? "不限额" : item.limit.ToString("N0"))}</td>
                <td><span class='expense-value'>{item.current:N0}</span></td>
            </tr>
            """);
            }
            description.AppendLine("</tbody></table>");
            description.AppendLine($"""
        <p>
            支出合计: 
            <span class='expense-value'>
                <strong>{yearlyExpenseItems.Sum(item => item.current):N0}</strong>
            </span>
        </p>
        """);
        }

        #endregion

        #region 总结

        description.AppendLine("<h3>存款计划结论</h3>");
        var plannedIncome = monthlyIncomeItems.Sum(item => item.limit == 0 ? item.current : item.limit) + yearlyIncomeItems.Sum(item => item.current);
        var plannedExpense = monthlyExpenseItems.Sum(item => item.limit == 0 ? item.current : item.limit) + yearlyExpenseItems.Sum(item => item.current);
        var expectedSavings = plannedIncome - plannedExpense;
        description.AppendLine($"""
        <p>
            计划存款: 
            <span class='income-value'>
                <strong>{expectedSavings:N0}</strong>
            </span>
            = 
        </p>
        <p>
            &nbsp;&nbsp;&nbsp;&nbsp;
            计划收入:
            <span class='income-value'>
                <strong>{monthlyIncomeItems.Sum(item => item.limit):N0}</strong>
            </span>
        </p>
        """);
        if (yearlyIncomeItems.Count > 0)
        {
            description.AppendLine($"""
        <p>
            &nbsp;&nbsp;&nbsp;&nbsp;
            + 本月发生的年度预算收入:
            <span class='income-value'>
                <strong>{yearlyIncomeItems.Sum(item => item.current):N0}</strong>
            </span>
        </p>
        """);
        }
        description.AppendLine($"""
        <p>
            &nbsp;&nbsp;&nbsp;&nbsp;
            - 计划支出:
            <span class='expense-value'>
                <strong>{monthlyExpenseItems.Sum(item => item.limit):N0}</strong>
            </span>
        """);
        if (yearlyExpenseItems.Count > 0)
        {
            description.AppendLine($"""
        <p>
            &nbsp;&nbsp;&nbsp;&nbsp;    
            - 本月发生的年度预算支出:
            <span class='expense-value'>
                <strong>{yearlyExpenseItems.Sum(item => item.current):N0}</strong>
            </span>
        </p>
        """);
        }
        description.AppendLine($"""
        </p>
        """);
        #endregion

        var savingsCategories = await configService.GetConfigByKeyAsync<string>("SavingsCategories") ?? string.Empty;
        var currentActual = 0m;
        if (!string.IsNullOrEmpty(savingsCategories))
        {
             var cats = new HashSet<string>(savingsCategories.Split(',', StringSplitOptions.RemoveEmptyEntries));
             foreach(var kvp in transactionClassify)
             {
                 if (cats.Contains(kvp.Key.Item1))
                 {
                     currentActual += kvp.Value;
                 }
             }
        }

        var record = new BudgetRecord
        {
            Id = -2,
            Name = "月度存款计划",
            Type = BudgetPeriodType.Month,
            Limit = expectedSavings,
            Category = BudgetCategory.Savings,
            SelectedCategories = savingsCategories,
            StartDate = new DateTime(year, month, 1),
            NoLimit = false,
            IsMandatoryExpense = false,
            CreateTime = dateTimeProvider.Now,
            UpdateTime = dateTimeProvider.Now
        };

        return BudgetResult.FromEntity(
            record,
            currentActual,
            new DateTime(year, month, 1),
            description.ToString()
        );
    }

    private async Task<BudgetResult> GetForYearAsync(
        IEnumerable<BudgetRecord> budgets,
        int year)
    {
        // 因为非当前月份的读取归档数据,这边依然是读取当前月份的数据
        var currentMonth = dateTimeProvider.Now.Month;
        var transactionClassify = await transactionStatisticsService.GetAmountGroupByClassifyAsync(
            new DateTime(year, currentMonth, 1),
            new DateTime(year, currentMonth, 1).AddMonths(1)
        );

        var currentMonthlyIncomeItems = new List<(long id, string name, decimal limit, int factor, decimal current, bool isMandatory)>();
        var currentYearlyIncomeItems = new List<(long id, string name, decimal limit, int factor, decimal current, bool isMandatory)>();
        var currentMonthlyExpenseItems = new List<(long id, string name, decimal limit, int factor, decimal current, bool isMandatory)>();
        var currentYearlyExpenseItems = new List<(long id, string name, decimal limit, int factor, decimal current, bool isMandatory)>();
        // 归档的预算收入支出明细
        var archiveIncomeItems = new List<(long id, string name, int[] months, decimal limit, decimal current)>();
        var archiveExpenseItems = new List<(long id, string name, int[] months, decimal limit, decimal current)>();
        // 获取归档数据
        var archives = await budgetArchiveRepository.GetArchivesByYearAsync(year);
        var archiveBudgetGroups = archives
            .SelectMany(a => a.Content.Select(x => (a.Month, Archive: x)))
            .Where(b => b.Archive.Type == BudgetPeriodType.Month) // 因为本来就是当前年度预算的生成 ,归档无需关心年度, 以最新地为准即可
            .GroupBy(b => (b.Archive.Id, b.Archive.Limit));

        foreach (var archiveBudgetGroup in archiveBudgetGroups)
        {
            var (_, archive) = archiveBudgetGroup.First();
            var archiveItems = archive.Category switch
            {
                BudgetCategory.Income => archiveIncomeItems,
                BudgetCategory.Expense => archiveExpenseItems,
                _ => throw new NotSupportedException($"Category {archive.Category} is not supported.")
            };

            archiveItems.Add((
                id: archiveBudgetGroup.Key.Id,
                name: archive.Name,
                months: archiveBudgetGroup.Select(x => x.Month).OrderBy(m => m).ToArray(),
                limit: archiveBudgetGroup.Key.Limit,
                current: archiveBudgetGroup.Sum(x => x.Archive.Actual)
            ));
        }

        // 处理当月最新地没有归档的预算
        foreach (var budget in budgets)
        {
            var currentAmount = 0m;

            var classifyList = budget.SelectedCategories.Split(',', StringSplitOptions.RemoveEmptyEntries);

            var transactionType = budget.Category switch
            {
                BudgetCategory.Income => TransactionType.Income,
                BudgetCategory.Expense => TransactionType.Expense,
                _ => throw new NotSupportedException($"Budget category {budget.Category} is not supported.")
            };

            foreach (var classify in classifyList)
            {
                // 获取分类+收入支出类型一致的金额
                if (transactionClassify.TryGetValue((classify, transactionType), out var amount))
                {
                    currentAmount += amount;
                }
            }

            // 硬性预算 如果实际发生金额小于 应(总天数/实际天数)发生金额 
            // 直接取应发生金额(为了预算的准确性)
            if (budget.IsMandatoryExpense && currentAmount == 0)
            {
                currentAmount = budget.IsMandatoryExpense && currentAmount == 0
                    ? budget.Limit / (DateTime.IsLeapYear(year) ? 366 : 365) * dateTimeProvider.Now.DayOfYear
                    : budget.Limit / DateTime.DaysInMonth(year, currentMonth) * dateTimeProvider.Now.Day;
            }

            AddOrIncCurrentItem(
                budget.Id,
                budget.Type,
                budget.Category,
                budget.Name,
                budget.Limit,
                budget.Type == BudgetPeriodType.Year
                ? 1
                : 12 - currentMonth + 1,
                currentAmount,
                budget.IsMandatoryExpense
            );
        }

        var description = new StringBuilder();

        #region 构建归档收入明细表格
        var archiveIncomeDiff = 0m;
        if (archiveIncomeItems.Any())
        {
            description.AppendLine("<h3>已归档收入明细</h3>");
            description.AppendLine("""
        <table>
            <thead>
                <tr>
                    <th>名称</th>
                    <th>预算</th>
                    <th>月</th>
                    <th>合计</th>
                    <th>实际</th>
                </tr>
            </thead>
            </tbody>
        """);
            // 已归档的收入
            foreach (var (_, name, months, limit, current) in archiveIncomeItems)
            {
                description.AppendLine($"""
                <tr>
                    <td>{name}</td>
                    <td>{(limit == 0 ? "不限额" : limit.ToString("N0"))}</td>
                    <td>{FormatMonths(months)}</td>
                    <td>{limit * months.Length:N0}</td>
                    <td><span class='income-value'>{current:N0}</span></td>
                </tr>
            """);
            }
            description.AppendLine("</tbody></table>");
            archiveIncomeDiff = archiveIncomeItems.Sum(i => i.current) - archiveIncomeItems.Sum(i => i.limit * i.months.Length);
            description.AppendLine($"""
        <p>
            <span class="highlight">已归档收入总结: </span>
            {(archiveIncomeDiff > 0 ? "超额收入" : "未达预期")}:
            <span class='{(archiveIncomeDiff > 0 ? "income-value" : "expense-value")}'>
                <strong>{archiveIncomeDiff:N0}</strong>
            </span>
            = 
            <span class='income-value'>
                <strong>{archiveIncomeItems.Sum(i => i.limit * i.months.Length):N0}</strong>
            </span>
            - 
            实际收入合计:
            <span class='income-value'>
                <strong>{archiveIncomeItems.Sum(i => i.current):N0}</strong>
            </span>
        </p>
        """);
        }
        #endregion

        #region 构建年度预算收入明细表格
        description.AppendLine("<h3>预算收入明细</h3>");
        description.AppendLine("""
        <table>
            <thead>
                <tr>
                    <th>名称</th>
                    <th>预算</th>
                    <th>月/年</th>
                    <th>合计</th>
                </tr>
            </thead>
            <tbody>
        """);

        // 当前预算
        foreach (var (_, name, limit, factor, _, _) in currentMonthlyIncomeItems)
        {
            description.AppendLine($"""
            <tr>
                <td>{name}</td>
                <td>{(limit == 0 ? "不限额" : limit.ToString("N0"))}</td>
                <td>{FormatMonthsByFactor(factor)}</td>
                <td>{(limit == 0 ? "不限额" : (limit * factor).ToString("N0"))}</td>
            </tr>
            """);
        }

        // 年预算
        foreach (var (_, name, limit, _, _, _) in currentYearlyIncomeItems)
        {
            description.AppendLine($"""
            <tr>
                <td>{name}</td>
                <td>{(limit == 0 ? "不限额" : limit.ToString("N0"))}</td>
                <td>{year}年</td>
                <td>{(limit == 0 ? "不限额" : limit.ToString("N0"))}</td>
            </tr>
            """);
        }
        description.AppendLine("</tbody></table>");
        
        description.AppendLine($"""
        <p>
            预算收入合计:
            <span class='expense-value'>
                <strong>
                    {
                        currentMonthlyIncomeItems.Sum(i => i.limit * i.factor) 
                        + currentYearlyIncomeItems.Sum(i => i.limit)
                    :N0}
                </strong>
            </span>    
        </p>
        """);
        #endregion

        #region 构建年度归档支出明细表格
        var archiveExpenseDiff = 0m;
        if (archiveExpenseItems.Any())
        {
            description.AppendLine("<h3>已归档支出明细</h3>");
            description.AppendLine("""
        <table>
            <thead>
                <tr>
                    <th>名称</th>
                    <th>预算</th>
                    <th>月</th>
                    <th>合计</th>
                    <th>实际</th>
                </tr>
            </thead>
            <tbody>
        """);

            // 已归档的支出
            foreach (var (_, name, months, limit, current) in archiveExpenseItems)
            {
                description.AppendLine($"""
                <tr>
                    <td>{name}</td>
                    <td>{(limit == 0 ? "不限额" : limit.ToString("N0"))}</td>
                    <td>{FormatMonths(months)}</td>
                    <td>{limit * months.Length:N0}</td>
                    <td><span class='expense-value'>{current:N0}</span></td>
                </tr>
                """);
            }
            description.AppendLine("</tbody></table>");
            
            archiveExpenseDiff = archiveExpenseItems.Sum(i => i.limit * i.months.Length) - archiveExpenseItems.Sum(i => i.current);
            description.AppendLine($"""
        <p>
            <span class="highlight">已归档支出总结: </span>
            {(archiveExpenseDiff > 0 ? "节省支出" : "超支")}:
            <span class='{(archiveExpenseDiff > 0 ? "income-value" : "expense-value")}'>
                <strong>{archiveExpenseDiff:N0}</strong>
            </span>
            = 
            <span class='expense-value'>
                <strong>{archiveExpenseItems.Sum(i => i.limit * i.months.Length):N0}</strong>
            </span>
            - 实际支出合计:
            <span class='expense-value'>
                <strong>{archiveExpenseItems.Sum(i => i.current):N0}</strong>
            </span>
        </p>
        """);
        }
        #endregion

        #region 构建当前年度预算支出明细表格
        description.AppendLine("<h3>预算支出明细</h3>");
        description.AppendLine("""
        <table>
            <thead>
                <tr>
                    <th>名称</th>
                    <th>预算</th>
                    <th>月/年</th>
                    <th>合计</th>
                </tr>
            </thead>
            <tbody>
        """);

        // 未来月预算
        foreach (var (_, name, limit, factor, _, _) in currentMonthlyExpenseItems)
        {
            description.AppendLine($"""
            <tr>
                <td>{name}</td>
                <td>{(limit == 0 ? "不限额" : limit.ToString("N0"))}</td>
                <td>{FormatMonthsByFactor(factor)}</td>
                <td>{(limit == 0 ? "不限额" : (limit * factor).ToString("N0"))}</td>
            </tr>
            """);
        }

        // 年预算
        foreach (var (_, name, limit, _, _, _) in currentYearlyExpenseItems)
        {
            description.AppendLine($"""
            <tr>
                <td>{name}</td>
                <td>{(limit == 0 ? "不限额" : limit.ToString("N0"))}</td>
                <td>{year}年</td>
                <td>{(limit == 0 ? "不限额" : limit.ToString("N0"))}</td>
            </tr>
            """);
        }
        description.AppendLine("</tbody></table>");

        // 合计
        description.AppendLine($"""
        <p>
            支出预算合计:
            <span class='expense-value'>
                <strong>
                {
                    currentMonthlyExpenseItems.Sum(i => i.limit * i.factor) 
                    + currentYearlyExpenseItems.Sum(i => i.limit)
                :N0}
               </strong>
            </span>
        </p>
        """);
        #endregion

        #region 总结
        var archiveIncomeBudget = archiveIncomeItems.Sum(i => i.limit * i.months.Length);
        var archiveExpenseBudget = archiveExpenseItems.Sum(i => i.limit * i.months.Length);
        var archiveSavings = archiveIncomeBudget - archiveExpenseBudget + archiveIncomeDiff + archiveExpenseDiff;

        var expectedIncome = currentMonthlyIncomeItems.Sum(i => i.limit == 0 ? i.current : i.limit * i.factor) + currentYearlyIncomeItems.Sum(i => i.isMandatory || i.limit == 0 ? i.current : i.limit);
        var expectedExpense = currentMonthlyExpenseItems.Sum(i => i.limit == 0 ? i.current : i.limit * i.factor) + currentYearlyExpenseItems.Sum(i => i.isMandatory || i.limit == 0 ? i.current : i.limit);
        var expectedSavings = expectedIncome - expectedExpense;

        description.AppendLine("<h3>存款计划结论</h3>");
        description.AppendLine($"""
        <p>
            <strong>归档存款：</strong>
            <span class='income-value'><strong>{archiveSavings:N0}</strong></span>
            = 
            归档收入: <span class='income-value'>{archiveIncomeBudget:N0}</span>
            - 
            归档支出: <span class='expense-value'>{archiveExpenseBudget:N0}</span>
            {(archiveIncomeDiff >= 0 ? " + 超额收入" : " - 未达预期收入")}: <span class='{(archiveIncomeDiff >= 0 ? "income-value" : "expense-value")}'>{(archiveIncomeDiff >= 0 ? archiveIncomeDiff : -archiveIncomeDiff):N0}</span>
            {(archiveExpenseDiff >= 0 ? " + 节省支出" : " - 超额支出")}: <span class='{(archiveExpenseDiff >= 0 ? "income-value" : "expense-value")}'>{(archiveExpenseDiff >= 0 ? archiveExpenseDiff : -archiveExpenseDiff):N0}</span>
        </p>
        <p>
            <strong>预计存款：</strong>
            <span class='income-value'><strong>{expectedSavings:N0}</strong></span>
            =
            预计收入: <span class='income-value'>{expectedIncome:N0}</span>
            - 
            预计支出: <span class='expense-value'>{expectedExpense:N0}</span>
        </p>
        <p>
            <strong>存档总结：</strong>
            <span class='{(archiveSavings + expectedSavings > 0 ? "income-value" : "expense-value")}'>
                <strong>{archiveSavings + expectedSavings:N0}</strong>
            </span>
            =
            预计存款: 
            <span class='income-value'>{expectedSavings:N0}</span>
            {(archiveSavings > 0 ? "+" : "-")}
            归档存款: 
            <span class='{(archiveSavings > 0 ? "income-value" : "expense-value")}'>{Math.Abs(archiveSavings):N0}</span>
        </p>
        """);
        #endregion

        var savingsCategories = await configService.GetConfigByKeyAsync<string>("SavingsCategories") ?? string.Empty;
        
        var currentActual = 0m;
        if (!string.IsNullOrEmpty(savingsCategories))
        {
             var cats = new HashSet<string>(savingsCategories.Split(',', StringSplitOptions.RemoveEmptyEntries));
             foreach(var kvp in transactionClassify)
             {
                 if (cats.Contains(kvp.Key.Item1))
                 {
                     currentActual += kvp.Value;
                 }
             }
        }

        var record = new BudgetRecord
        {
            Id = -1,
            Name = "年度存款计划",
            Type = BudgetPeriodType.Year,
            Limit = archiveSavings + expectedSavings,
            Category = BudgetCategory.Savings,
            SelectedCategories = savingsCategories,
            StartDate = new DateTime(year, 1, 1),
            NoLimit = false,
            IsMandatoryExpense = false,
            CreateTime = dateTimeProvider.Now,
            UpdateTime = dateTimeProvider.Now
        };

        return BudgetResult.FromEntity(
            record,
            currentActual,
            new DateTime(year, 1, 1),
            description.ToString()
        );

        void AddOrIncCurrentItem(
            long id,
            BudgetPeriodType periodType,
            BudgetCategory category,
            string name,
            decimal limit,
            int factor,
            decimal incAmount,
            bool isMandatory)
        {
            var current = (periodType, category) switch
            {
                (BudgetPeriodType.Month, BudgetCategory.Income) => currentMonthlyIncomeItems,
                (BudgetPeriodType.Month, BudgetCategory.Expense) => currentMonthlyExpenseItems,
                (BudgetPeriodType.Year, BudgetCategory.Income) => currentYearlyIncomeItems,
                (BudgetPeriodType.Year, BudgetCategory.Expense) => currentYearlyExpenseItems,
                _ => throw new NotSupportedException($"Category {category} is not supported.")
            };

            if (current.Any(i => i.id == id))
            {
                var existing = current.First(i => i.id == id);
                current.Remove(existing);
                current.Add((id, existing.name, existing.limit, existing.factor + factor, existing.current + incAmount, isMandatory));
            }
            else
            {
                current.Add((id, name, limit, factor, incAmount, isMandatory));
            }
        }

        string FormatMonthsByFactor(int factor)
        {
            var months = factor == 12
                ? Enumerable.Range(1, 12).ToArray()
                : Enumerable.Range(dateTimeProvider.Now.Month, factor).ToArray();

            return FormatMonths(months.ToArray());
        }

        string FormatMonths(int[] months)
        {
            // 如果是连续的月份 则简化显示 1~3
            Array.Sort(months);
            if (months.Length >= 2)
            {
                var isContinuous = true;
                for (var i = 1; i < months.Length; i++)
                {
                    if (months[i] != months[i - 1] + 1)
                    {
                        isContinuous = false;
                        break;
                    }
                }

                if (isContinuous)
                {
                    return $"{months.First()}~{months.Last()}月";
                }
            }

            return string.Join(", ", months) + "月";
        }
    }
}