using Service.Transaction;

namespace WebApi.Test.Transaction;

public class TransactionStatisticsServiceTest : BaseTest
{
    private readonly ITransactionRecordRepository _transactionRepository = Substitute.For<ITransactionRecordRepository>();
    private readonly ITransactionStatisticsService _service;

    public TransactionStatisticsServiceTest()
    {
        // 默认配置 QueryAsync 返回空列表
        _transactionRepository.QueryAsync(
            Arg.Any<int?>(),
            Arg.Any<int?>(),
            Arg.Any<DateTime?>(),
            Arg.Any<DateTime?>(),
            Arg.Any<TransactionType?>(),
            Arg.Any<string[]>(),
            Arg.Any<string>(),
            Arg.Any<string>(),
            Arg.Any<int>(),
            Arg.Any<int>(),
            Arg.Any<bool>())
            .ReturnsForAnyArgs(new List<TransactionRecord>());

        _service = new TransactionStatisticsService(
            _transactionRepository
        );
    }

    private void ConfigureQueryAsync(List<TransactionRecord> data)
    {
        _transactionRepository.QueryAsync(
            Arg.Any<int?>(),
            Arg.Any<int?>(),
            Arg.Any<DateTime?>(),
            Arg.Any<DateTime?>(),
            Arg.Any<TransactionType?>(),
            Arg.Any<string[]>(),
            Arg.Any<string>(),
            Arg.Any<string>(),
            Arg.Any<int>(),
            Arg.Any<int>(),
            Arg.Any<bool>())
            .ReturnsForAnyArgs(data);
    }

    [Fact]
    public async Task GetDailyStatisticsAsync_基本测试()
    {
        // Arrange
        var year = 2024;
        var month = 1;
        var testData = new List<TransactionRecord>
        {
            new() { Id=1, OccurredAt=new DateTime(2024,1,1), Amount=-100m, Type=TransactionType.Expense },
            new() { Id=2, OccurredAt=new DateTime(2024,1,1), Amount=-50m, Type=TransactionType.Expense },
            new() { Id=3, OccurredAt=new DateTime(2024,1,2), Amount=5000m, Type=TransactionType.Income }
        };

        ConfigureQueryAsync(testData);

        // Act
        var result = await _service.GetDailyStatisticsAsync(year, month);

        // Assert
        result.Should().ContainKey("2024-01-01");
        result["2024-01-01"].expense.Should().Be(150m);
    }

    [Fact]
    public async Task GetTrendStatisticsAsync_多个月份()
    {
        // Arrange
        var startYear = 2024;
        var startMonth = 1;
        var monthCount = 3;

        var allRecords = new List<TransactionRecord>
        {
            // Month 1
            new() { Id = 1, OccurredAt = new DateTime(2024, 1, 1), Amount = -1000m, Type = TransactionType.Expense },
            new() { Id = 2, OccurredAt = new DateTime(2024, 1, 5), Amount = 5000m, Type = TransactionType.Income },
            // Month 2
            new() { Id = 3, OccurredAt = new DateTime(2024, 2, 1), Amount = -1500m, Type = TransactionType.Expense },
            new() { Id = 4, OccurredAt = new DateTime(2024, 2, 5), Amount = 5000m, Type = TransactionType.Income },
            // Month 3
            new() { Id = 5, OccurredAt = new DateTime(2024, 3, 1), Amount = -2000m, Type = TransactionType.Expense },
            new() { Id = 6, OccurredAt = new DateTime(2024, 3, 5), Amount = 5000m, Type = TransactionType.Income }
        };

        // Mock Logic: filter by year (Arg[0]) and month (Arg[1]) and type (Arg[4]) if provided
        _transactionRepository.QueryAsync(
            Arg.Any<int?>(), Arg.Any<int?>(), Arg.Any<DateTime?>(), Arg.Any<DateTime?>(), Arg.Any<TransactionType?>(), Arg.Any<string[]>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<bool>()
        ).Returns(callInfo => 
        {
            var y = callInfo.ArgAt<int?>(0);
            var m = callInfo.ArgAt<int?>(1);
            var type = callInfo.ArgAt<TransactionType?>(4);

            var query = allRecords.AsEnumerable();
            if (y.HasValue) query = query.Where(t => t.OccurredAt.Year == y.Value);
            if (m.HasValue) query = query.Where(t => t.OccurredAt.Month == m.Value);
            // Service calls QueryAsync with 'type' parameter? 
            // In GetTrendStatisticsAsync: transactionRepository.QueryAsync(year: targetYear, month: targetMonth...)
            // It does NOT pass type. So type is null.
            // But Service THEN filters by Type in memory.
            
            return query.ToList();
        });

        // Act
        var result = await _service.GetTrendStatisticsAsync(startYear, startMonth, monthCount);

        // Assert
        result.Should().HaveCount(3);
        result[0].Month.Should().Be(1);
        result[0].Expense.Should().Be(1000m); // Abs(-1000)
    }

    [Fact]
    public async Task GetReasonGroupsAsync_基本测试()
    {
        // Arrange
        var testData = new List<TransactionRecord>
        {
            new() { Id=1, Reason="M", Classify="", Amount=-50m, Type=TransactionType.Expense },
            new() { Id=2, Reason="M", Classify="", Amount=-80m, Type=TransactionType.Expense }
        };

        ConfigureQueryAsync(testData);

        // Act
        var result = await _service.GetReasonGroupsAsync();

        // Assert
        var item = result.list.First(x => x.Reason == "M");
        item.TotalAmount.Should().Be(130m); // Expect positive (Abs) as per Service logic
    }

    [Fact]
    public async Task GetClassifiedByKeywordsWithScoreAsync_基本测试()
    {
        // Arrange
        var keywords = new List<string> { "麦当劳" };
        var testData = new List<TransactionRecord>
        {
            new() { Id=1, Reason="麦当劳午餐", Classify="餐饮" }
        };

        // Needs to mock GetClassifiedByKeywordsAsync
        _transactionRepository.GetClassifiedByKeywordsAsync(Arg.Any<List<string>>(), Arg.Any<int>())
            .Returns(Task.FromResult(testData));

        // Act
        var result = await _service.GetClassifiedByKeywordsWithScoreAsync(keywords);

        // Assert
        result.Should().HaveCount(1);
        result[0].record.Reason.Should().Contain("麦当劳");
    }

    [Fact]
    public async Task GetAmountGroupByClassifyAsync_基本测试()
    {
        // Arrange
        var testData = new List<TransactionRecord>
        {
            new() { Amount=-100m, Type=TransactionType.Expense, Classify="餐饮" },
            new() { Amount=-50m, Type=TransactionType.Expense, Classify="餐饮" }
        };
        ConfigureQueryAsync(testData);

        // Act
        var result = await _service.GetAmountGroupByClassifyAsync(DateTime.Now, DateTime.Now);

        // Assert
        result[("餐饮", TransactionType.Expense)].Should().Be(-150m); // Expect Negative (Sum of amounts)
    }
    
    // Additional tests from original file to maintain coverage, with minimal adjustments if needed
    [Fact]
    public async Task GetCategoryStatisticsAsync_支出分类()
    {
        var year = 2024; var month = 1;
        var testData = new List<TransactionRecord>
        {
            new() { Amount = -100m, Type = TransactionType.Expense, Classify = "餐饮" },
            new() { Amount = -50m, Type = TransactionType.Expense, Classify = "餐饮" },
            new() { Amount = -200m, Type = TransactionType.Expense, Classify = "交通" }
        };
        
        // Mock filtering by Type
         _transactionRepository.QueryAsync(
            Arg.Any<int?>(), Arg.Any<int?>(), Arg.Any<DateTime?>(), Arg.Any<DateTime?>(), Arg.Any<TransactionType?>(), Arg.Any<string[]>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<bool>()
        ).Returns(callInfo => 
        {
            var type = callInfo.ArgAt<TransactionType?>(4);
            return testData.Where(t => !type.HasValue || t.Type == type).ToList();
        });

        var result = await _service.GetCategoryStatisticsAsync(year, month, TransactionType.Expense);
        
        result.First(c => c.Classify == "餐饮").Amount.Should().Be(150m);
        result.First(c => c.Classify == "交通").Amount.Should().Be(200m);
    }
}
