using Service.Transaction;

namespace WebApi.Test.Budget;

public class BudgetSavingsTest : BaseTest
{
    private readonly IBudgetRepository _budgetRepository = Substitute.For<IBudgetRepository>();
    private readonly IBudgetArchiveRepository _budgetArchiveRepository = Substitute.For<IBudgetArchiveRepository>();
    private readonly ITransactionStatisticsService _transactionStatisticsService = Substitute.For<ITransactionStatisticsService>();
    private readonly IConfigService _configService = Substitute.For<IConfigService>();
    private readonly IDateTimeProvider _dateTimeProvider = Substitute.For<IDateTimeProvider>();
    private readonly BudgetSavingsService _service;

    public BudgetSavingsTest()
    {
        _dateTimeProvider.Now.Returns(DateTime.Now);
        _service = new BudgetSavingsService(
            _budgetRepository,
            _budgetArchiveRepository,
            _transactionStatisticsService,
            _configService,
            _dateTimeProvider
        );
    }

    [Fact]
    public async Task GetSavings_月度_Test()
    {
        // Arrange
        var referenceDate = new DateTime(2024, 1, 1);
        var budgets = new List<BudgetRecord>
        {
            new()
            {
                Id = 1, Name = "工资", Type = BudgetPeriodType.Month, Limit = 10000, Category = BudgetCategory.Income,
                SelectedCategories = "工资"
            },
            new()
            {
                Id = 2, Name = "餐饮", Type = BudgetPeriodType.Month, Limit = 2000, Category = BudgetCategory.Expense,
                SelectedCategories = "餐饮"
            }
        };

        var transactions = new Dictionary<(string, TransactionType), decimal>
        {
            { ("工资", TransactionType.Income), 10000m },
            { ("餐饮", TransactionType.Expense), 1500m }
        };

        _budgetRepository.GetAllAsync().Returns(budgets);
        _transactionStatisticsService.GetAmountGroupByClassifyAsync(Arg.Any<DateTime>(), Arg.Any<DateTime>())
            .Returns(transactions);
        _configService.GetConfigByKeyAsync<string>("SavingsCategories").Returns(Task.FromResult<string?>("存款"));

        // Act
        var result = await _service.GetSavingsDtoAsync(BudgetPeriodType.Month, referenceDate);

        // Assert
        result.Should().NotBeNull();
        result.Limit.Should().Be(10000 - 2000); // 计划收入 - 计划支出 = 10000 - 2000 = 8000
    }

    [Fact]
    public async Task GetSavings_月度_年度收支_Test()
    {
        // Arrange
        var referenceDate = new DateTime(2024, 1, 1);
        var budgets = new List<BudgetRecord>
        {
            new()
            {
                Id = 1, Name = "工资", Type = BudgetPeriodType.Month, Limit = 10000, Category = BudgetCategory.Income,
                SelectedCategories = "工资"
            },
            new()
            {
                Id = 2, Name = "餐饮", Type = BudgetPeriodType.Month, Limit = 2000, Category = BudgetCategory.Expense,
                SelectedCategories = "餐饮"
            },
            new()
            {
                Id = 3, Name = "年终奖", Type = BudgetPeriodType.Year, Limit = 50000, Category = BudgetCategory.Income,
                SelectedCategories = "奖金"
            },
            new()
            {
                Id = 4, Name = "保险", Type = BudgetPeriodType.Year, Limit = 6000, Category = BudgetCategory.Expense,
                SelectedCategories = "保险"
            }
        };

        var transactions = new Dictionary<(string, TransactionType), decimal>
        {
            { ("工资", TransactionType.Income), 10000m },
            { ("餐饮", TransactionType.Expense), 1500m },
            { ("奖金", TransactionType.Income), 50000m },
            { ("保险", TransactionType.Expense), 6000m }
        };

        _budgetRepository.GetAllAsync().Returns(budgets);
        _transactionStatisticsService.GetAmountGroupByClassifyAsync(Arg.Any<DateTime>(), Arg.Any<DateTime>())
            .Returns(transactions);
        _configService.GetConfigByKeyAsync<string>("SavingsCategories").Returns(Task.FromResult<string?>("存款"));

        // Act
        var result = await _service.GetSavingsDtoAsync(BudgetPeriodType.Month, referenceDate);

        // Assert
        result.Should().NotBeNull();
        // 计划收入 = 月度计划收入(10000) + 本月发生的年度实际收入(50000) = 60000
        // 计划支出 = 月度计划支出(2000) + 本月发生的年度实际支出(6000) = 8000
        // 计划存款 = 60000 - 8000 = 52000
        result.Limit.Should().Be(60000 - 8000);
    }

    [Fact]
    public async Task GetSavings_月度_年度收支_硬性收支_Test()
    {
        // Arrange
        // 模拟当前日期为 2026-01-20
        var now = new DateTime(2026, 1, 20);
        _dateTimeProvider.Now.Returns(now);

        var referenceDate = new DateTime(2026, 1, 1);
        var budgets = new List<BudgetRecord>
        {
            // 房租 3100，硬性支出。假设目前还没付（实际为0），系统应按 20/31 天估算为 2000
            new()
            {
                Id = 1, Name = "房租", Type = BudgetPeriodType.Month, Limit = 3100, Category = BudgetCategory.Expense,
                SelectedCategories = "房租", IsMandatoryExpense = true
            },
            // 理财收益 6200，硬性收入。假设目前还没到账（实际为0），系统应按 20/31 天估算为 4000
            new()
            {
                Id = 2, Name = "理财收益", Type = BudgetPeriodType.Month, Limit = 6200, Category = BudgetCategory.Income,
                SelectedCategories = "理财", IsMandatoryExpense = true
            }
        };

        // 模拟实际交易为 0
        var transactions = new Dictionary<(string, TransactionType), decimal>();

        _budgetRepository.GetAllAsync().Returns(budgets);
        _transactionStatisticsService.GetAmountGroupByClassifyAsync(Arg.Any<DateTime>(), Arg.Any<DateTime>())
            .Returns(transactions);
        _configService.GetConfigByKeyAsync<string>("SavingsCategories").Returns(Task.FromResult<string?>("存款"));

        // Act
        var result = await _service.GetSavingsDtoAsync(BudgetPeriodType.Month, referenceDate);

        // Assert
        // 2026年1月有31天，当前是20号
        // 预期的估算值：
        // 支出 = 3100 / 31 * 20 = 2000
        // 收入 = 6200 / 31 * 20 = 4000

        result.Should().NotBeNull();
        // 计划存款 = 计划收入(6200) - 计划支出(3100) = 3100
        result.Limit.Should().Be(6200 - 3100);
    }

    [Fact]
    public async Task GetSavings_年度_预算_实际_Test()
    {
        // Arrange
        var year = 2024;
        var referenceDate = new DateTime(year, 1, 1);
        _dateTimeProvider.Now.Returns(new DateTime(year, 1, 20));

        var budgets = new List<BudgetRecord>
        {
            new() { Id = 1, Name = "工资", Type = BudgetPeriodType.Month, Limit = 10000, Category = BudgetCategory.Income, SelectedCategories = "工资" },
            new() { Id = 2, Name = "房租", Type = BudgetPeriodType.Month, Limit = 3000, Category = BudgetCategory.Expense, SelectedCategories = "房租" },
            new() { Id = 3, Name = "年终奖", Type = BudgetPeriodType.Year, Limit = 50000, Category = BudgetCategory.Income, SelectedCategories = "奖金" },
            new() { Id = 4, Name = "旅游", Type = BudgetPeriodType.Year, Limit = 20000, Category = BudgetCategory.Expense, SelectedCategories = "旅游" }
        };

        var transactions = new Dictionary<(string, TransactionType), decimal>
        {
            { ("工资", TransactionType.Income), 10000m },
            { ("房租", TransactionType.Expense), 3000m },
            { ("存款", TransactionType.None), 2000m }
        };

        _budgetRepository.GetAllAsync().Returns(budgets);
        _transactionStatisticsService.GetAmountGroupByClassifyAsync(Arg.Any<DateTime>(), Arg.Any<DateTime>())
            .Returns(transactions);
        _budgetArchiveRepository.GetArchivesByYearAsync(year).Returns(new List<BudgetArchive>());
        _configService.GetConfigByKeyAsync<string>("SavingsCategories").Returns(Task.FromResult<string?>("存款"));

        // Act
        var result = await _service.GetSavingsDtoAsync(BudgetPeriodType.Year, referenceDate);

        // Assert
        result.Should().NotBeNull();
        // MonthlyIncome: 10000 * 12 = 170000
        // MonthlyExpense: 3000 * 12 = 56000
        // YearlyIncome: 50000 * 1 = 50000
        // YearlyExpense: 20000 * 1 = 20000
        // Savings: (170000 + 50000) - (56000 + 20000) = 114000
        result.Limit.Should().Be(114000);
        result.Current.Should().Be(2000);
        result.Name.Should().Be("年度存款计划");
    }

    [Fact]
    public async Task GetSavings_年度_归档盈亏_Test()
    {
        // Arrange
        var year = 2024;
        // 当前是3月15号
        _dateTimeProvider.Now.Returns(new DateTime(year, 3, 15));

        var budgets = new List<BudgetRecord>
        {
            // Monthly Budget changed from 10000 (Jan) to 11000 (Current/Feb)
            new() { Id = 1, Name = "工资", Type = BudgetPeriodType.Month, Limit = 11000, Category = BudgetCategory.Income, SelectedCategories = "工资" },
            new() { Id = 2, Name = "房租", Type = BudgetPeriodType.Month, Limit = 3000, Category = BudgetCategory.Expense, SelectedCategories = "房租" },
        };

        var currentTransactions = new Dictionary<(string, TransactionType), decimal>
        {
            { ("工资", TransactionType.Income), 11000m }
        };

        var archives = new List<BudgetArchive>
        {
            new()
            {
                Year = year, Month = 1,
                Content =
                [
                    new BudgetArchiveContent
                    {
                        Id = 1,
                        Name = "工资",
                        Type = BudgetPeriodType.Month,
                        Limit = 10000,
                        Actual = 12000,
                        Category = BudgetCategory.Income

                    },
                    new BudgetArchiveContent
                    {
                        Id = 2,
                        Name = "房租",
                        Type = BudgetPeriodType.Month,
                        Limit = 3000,
                        Actual = 3600,
                        Category = BudgetCategory.Expense
                    }
                ]
            },
            new()
            {
                Year = year, Month = 2,
                Content =
                [
                    new BudgetArchiveContent
                    {
                        Id = 1,
                        Name = "工资",
                        Type = BudgetPeriodType.Month,
                        Limit = 11000,
                        Actual = 3000,
                        Category = BudgetCategory.Income

                    },
                    new BudgetArchiveContent
                    {
                        Id = 2,
                        Name = "房租",
                        Type = BudgetPeriodType.Month,
                        Limit = 3000,
                        Actual = 5000,
                        Category = BudgetCategory.Expense
                    }
                ]
            }
        };

        _budgetRepository.GetAllAsync().Returns(budgets);
        _transactionStatisticsService.GetAmountGroupByClassifyAsync(Arg.Any<DateTime>(), Arg.Any<DateTime>())
            .Returns(currentTransactions);
        _budgetArchiveRepository.GetArchivesByYearAsync(year).Returns(archives);
        _configService.GetConfigByKeyAsync<string>("SavingsCategories").Returns(Task.FromResult<string?>("存款"));

        // Act
        var result = await _service.GetSavingsDtoAsync(BudgetPeriodType.Year, new DateTime(year, 1, 1));

        // Assert
        result.Should().NotBeNull();
        // 归档实际收入1月 = 12000 - 3600 = 8400
        // 归档实际收入2月 = 3000 - 5000 = -2000
        // 预计收入 = 8400 + -2000 + 11000 * 10 = 116400
        // 预计支出 = 3000 * 10 = 30000
        // 预计存款 = 116400 - 30000 = 86400
        result.Limit.Should().Be(86400);
    }

    [Fact]
    public async Task GetSavings_年度_硬性收支_Test()
    {
        // Arrange
        var year = 2024;
        // 当前是3月15号
        _dateTimeProvider.Now.Returns(new DateTime(year, 3, 15));

        var budgets = new List<BudgetRecord>
        {
            // Monthly Budget changed from 10000 (Jan) to 11000 (Current/Feb)
            new() { Id = 1, Name = "工资", Type = BudgetPeriodType.Month, Limit = 11000, Category = BudgetCategory.Income, SelectedCategories = "工资" },
            new() { Id = 2, Name = "房租", Type = BudgetPeriodType.Month, Limit = 3000, Category = BudgetCategory.Expense, SelectedCategories = "房租" },
            new() { Id = 3, Name = "硬性支出", Type = BudgetPeriodType.Year, Limit = 10000, Category = BudgetCategory.Expense, SelectedCategories = "房租", IsMandatoryExpense = true },
        };

        var currentTransactions = new Dictionary<(string, TransactionType), decimal>
        {
            { ("工资", TransactionType.Income), 11000m }
        };

        var archives = new List<BudgetArchive>
        {
            new()
            {
                Year = year, Month = 1,
                Content =
                [
                    new BudgetArchiveContent
                    {
                        Id = 1,
                        Name = "工资",
                        Type = BudgetPeriodType.Month,
                        Limit = 10000,
                        Actual = 12000,
                        Category = BudgetCategory.Income

                    },
                    new BudgetArchiveContent
                    {
                        Id = 2,
                        Name = "房租",
                        Type = BudgetPeriodType.Month,
                        Limit = 3000,
                        Actual = 3600,
                        Category = BudgetCategory.Expense
                    }
                ]
            },
            new()
            {
                Year = year, Month = 2,
                Content =
                [
                    new BudgetArchiveContent
                    {
                        Id = 1,
                        Name = "工资",
                        Type = BudgetPeriodType.Month,
                        Limit = 11000,
                        Actual = 3000,
                        Category = BudgetCategory.Income

                    },
                    new BudgetArchiveContent
                    {
                        Id = 2,
                        Name = "房租",
                        Type = BudgetPeriodType.Month,
                        Limit = 3000,
                        Actual = 5000,
                        Category = BudgetCategory.Expense
                    }
                ]
            }
        };

        _budgetRepository.GetAllAsync().Returns(budgets);
        _transactionStatisticsService.GetAmountGroupByClassifyAsync(Arg.Any<DateTime>(), Arg.Any<DateTime>())
            .Returns(currentTransactions);
        _budgetArchiveRepository.GetArchivesByYearAsync(year).Returns(archives);
        _configService.GetConfigByKeyAsync<string>("SavingsCategories").Returns(Task.FromResult<string?>("存款"));

        // Act
        var result = await _service.GetSavingsDtoAsync(BudgetPeriodType.Year, new DateTime(year, 1, 1));

        // Assert
        result.Should().NotBeNull();
        // 归档实际收入1月 = 12000 - 3600 = 8400
        // 归档实际收入2月 = 3000 - 5000 = -2000
        // 预计收入 = 8400 + -2000 + 11000 * 10 = 116400
        // 硬性支出平均到每天 = 10000 / 366 * 75 = 2049.18
        // 预计支出 = 3000 * 10 = 30000
        // 预计存款 = 116400 - 30000 - 2049.18 = 84350.82
        result.Limit.Should().BeApproximately(84350.82m, 0.01m);
    }

    [Fact]
    public async Task GetSavings_年度_不限额_Test()
    {
        // Arrange
        var year = 2024;
        // 当前是3月15号
        _dateTimeProvider.Now.Returns(new DateTime(year, 3, 15));

        var budgets = new List<BudgetRecord>
        {
            // Monthly Budget changed from 10000 (Jan) to 11000 (Current/Feb)
            new() { Id = 1, Name = "工资", Type = BudgetPeriodType.Month, Limit = 11000, Category = BudgetCategory.Income, SelectedCategories = "工资" },
            new() { Id = 2, Name = "房租", Type = BudgetPeriodType.Month, Limit = 3000, Category = BudgetCategory.Expense, SelectedCategories = "房租" },
            new() { Id = 3, Name = "硬性支出", Type = BudgetPeriodType.Year, Limit = 10000, Category = BudgetCategory.Expense, SelectedCategories = "房租", IsMandatoryExpense = true },
            new() { Id = 4, Name = "意外支出", Type = BudgetPeriodType.Year, Limit = 0, Category = BudgetCategory.Expense, SelectedCategories = "意外支出", NoLimit = true },
            new() { Id = 5, Name = "意外收入", Type = BudgetPeriodType.Month, Limit = 0, Category = BudgetCategory.Income, SelectedCategories = "意外收入", NoLimit = true }
        };

        var currentTransactions = new Dictionary<(string, TransactionType), decimal>
        {
            { ("工资", TransactionType.Income), 11000m },
            { ("意外支出", TransactionType.Expense), 300m },
            { ("意外收入", TransactionType.Income), 2000m },
        };

        var archives = new List<BudgetArchive>
        {
            new()
            {
                Year = year, Month = 1,
                Content =
                [
                    new BudgetArchiveContent
                    {
                        Id = 1,
                        Name = "工资",
                        Type = BudgetPeriodType.Month,
                        Limit = 10000,
                        Actual = 12000,
                        Category = BudgetCategory.Income

                    },
                    new BudgetArchiveContent
                    {
                        Id = 2,
                        Name = "房租",
                        Type = BudgetPeriodType.Month,
                        Limit = 3000,
                        Actual = 3600,
                        Category = BudgetCategory.Expense
                    }
                ]
            },
            new()
            {
                Year = year, Month = 2,
                Content =
                [
                    new BudgetArchiveContent
                    {
                        Id = 1,
                        Name = "工资",
                        Type = BudgetPeriodType.Month,
                        Limit = 11000,
                        Actual = 3000,
                        Category = BudgetCategory.Income

                    },
                    new BudgetArchiveContent
                    {
                        Id = 2,
                        Name = "房租",
                        Type = BudgetPeriodType.Month,
                        Limit = 3000,
                        Actual = 5000,
                        Category = BudgetCategory.Expense
                    }
                ]
            }
        };

        _budgetRepository.GetAllAsync().Returns(budgets);
        _transactionStatisticsService.GetAmountGroupByClassifyAsync(Arg.Any<DateTime>(), Arg.Any<DateTime>())
            .Returns(currentTransactions);
        _budgetArchiveRepository.GetArchivesByYearAsync(year).Returns(archives);
        _configService.GetConfigByKeyAsync<string>("SavingsCategories").Returns(Task.FromResult<string?>("存款"));

        // Act
        var result = await _service.GetSavingsDtoAsync(BudgetPeriodType.Year, new DateTime(year, 1, 1));

        // Assert
        result.Should().NotBeNull();
        // 归档实际收入1月 = 12000 - 3600 = 8400
        // 归档实际收入2月 = 3000 - 5000 = -2000
        // 预计收入 = 8400 + -2000 + 11000 * 10 = 116400
        // 硬性支出平均到每天 = 10000 / 366 * 75 = 2049.18
        // 预计支出 = 3000 * 10 = 30000
        // 预计意外支出 = 300
        // 预计意外收入 = 2000
        // 预计存款 = 116400 - 30000 - 2049.18 - 300 + 2000 = 86050.82
        result.Limit.Should().BeApproximately(86050.82m, 0.1m);
    }
}