namespace BudgetApp.Tests.Application;

using BudgetApp.Application.Services;
using BudgetApp.Domain.Interfaces;
using BudgetApp.Domain.Models;
using Moq;
using Xunit;

public class BudgetServiceTests
{
    private readonly Mock<IBudgetRepository> _mockRepository;
    private readonly BudgetService _service;

    public BudgetServiceTests()
    {
        _mockRepository = new Mock<IBudgetRepository>();
        _service = new BudgetService(_mockRepository.Object);
    }

    [Fact]
    public async Task CreateBaseBudgetAsync_WithValidParameters_CreatesAndSavesBudget()
    {
        // Arrange
        var name = "Grocery";
        var initialBalance = new Money(100m, "USD");

        _mockRepository.Setup(r => r.SaveAsync(It.IsAny<BaseBudget>()))
            .Returns(Task.CompletedTask);

        // Act
        var budget = await _service.CreateBaseBudgetAsync(name, initialBalance);

        // Assert
        Assert.NotNull(budget);
        Assert.Equal(name, budget.Name);
        Assert.Equal(100m, budget.Balance.Amount);
        _mockRepository.Verify(r => r.SaveAsync(It.IsAny<BaseBudget>()), Times.Once);
    }

    [Fact]
    public async Task CreateBaseBudgetAsync_WithNullName_ThrowsException()
    {
        // Arrange
        var initialBalance = new Money(100m, "USD");

        // Act & Assert
        await Assert.ThrowsAsync<ArgumentException>(() => 
            _service.CreateBaseBudgetAsync(null!, initialBalance));
    }

    [Fact]
    public async Task CreateGoalBudgetAsync_WithValidParameters_CreatesAndSavesBudget()
    {
        // Arrange
        var name = "Car Purchase";
        var initialBalance = new Money(0m, "USD");
        var goalAmount = new Money(30000m, "USD");
        var targetDate = DateTime.Now.AddYears(3);
        var periodicContribution = new Money(1000m, "USD");
        var contributionPeriod = TimeSpan.FromDays(30);

        _mockRepository.Setup(r => r.SaveAsync(It.IsAny<GoalBudget>()))
            .Returns(Task.CompletedTask);

        // Act
        var budget = await _service.CreateGoalBudgetAsync(
            name, initialBalance, goalAmount, targetDate, periodicContribution, contributionPeriod);

        // Assert
        Assert.NotNull(budget);
        Assert.Equal(name, budget.Name);
        Assert.Equal(30000m, budget.GoalAmount.Amount);
        _mockRepository.Verify(r => r.SaveAsync(It.IsAny<GoalBudget>()), Times.Once);
    }

    [Fact]
    public async Task GetBudgetByIdAsync_ReturnsBudget()
    {
        // Arrange
        var budgetId = Guid.NewGuid();
        var budget = new BaseBudget("Grocery", new Money(100m, "USD"));
        
        _mockRepository.Setup(r => r.GetByIdAsync(budgetId))
            .ReturnsAsync(budget);

        // Act
        var result = await _service.GetBudgetByIdAsync(budgetId);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(budget.Id, result.Id);
    }

    [Fact]
    public async Task GetAllBudgetsAsync_ReturnsAllBudgets()
    {
        // Arrange
        var budgets = new List<Budget>
        {
            new BaseBudget("Grocery", new Money(100m, "USD")),
            new BaseBudget("Medical", new Money(200m, "USD"))
        };

        _mockRepository.Setup(r => r.GetAllAsync())
            .ReturnsAsync(budgets);

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

        // Assert
        Assert.Equal(2, result.Count());
    }

    [Fact]
    public async Task GetBudgetBalanceAsync_ReturnsBalance()
    {
        // Arrange
        var budgetId = Guid.NewGuid();
        var budget = new BaseBudget("Grocery", new Money(100m, "USD"));
        
        _mockRepository.Setup(r => r.GetByIdAsync(budgetId))
            .ReturnsAsync(budget);

        // Act
        var balance = await _service.GetBudgetBalanceAsync(budgetId);

        // Assert
        Assert.NotNull(balance);
        Assert.Equal(100m, balance.Amount);
    }

    [Fact]
    public async Task GetTotalBudgetBalanceAsync_ReturnsSumOfAllBudgets()
    {
        // Arrange
        var budgets = new List<Budget>
        {
            new BaseBudget("Grocery", new Money(100m, "USD")),
            new BaseBudget("Medical", new Money(200m, "USD"))
        };

        _mockRepository.Setup(r => r.GetAllAsync())
            .ReturnsAsync(budgets);

        // Act
        var total = await _service.GetTotalBudgetBalanceAsync("USD");

        // Assert
        Assert.Equal(300m, total.Amount);
    }
}


