using FluentAssertions;
using NSubstitute;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Pricing.ChargeableChars;
using SIGCM2.Domain.Pricing.ChargeableChars;

namespace SIGCM2.Application.Tests.Pricing.ChargeableChars;

/// <summary>
/// PRC-001 — ChargeableCharConfigService tests.
/// Covers: per-medio wins over global, global fallback when no per-medio,
///         empty result when no config at all.
/// </summary>
public class ChargeableCharConfigServiceTests
{
    private readonly IChargeableCharConfigRepository _repo = Substitute.For<IChargeableCharConfigRepository>();
    private readonly ChargeableCharConfigService _service;

    private static readonly DateOnly AsOf = new(2026, 4, 20);

    public ChargeableCharConfigServiceTests()
    {
        _service = new ChargeableCharConfigService(_repo);
    }

    private static ChargeableCharConfig GlobalConfig(string symbol, decimal price) =>
        ChargeableCharConfig.Rehydrate(10L, null, symbol, ChargeableCharCategories.Currency, price, AsOf, null, true);

    private static ChargeableCharConfig ProductTypeConfig(long id, int productTypeId, string symbol, decimal price) =>
        ChargeableCharConfig.Rehydrate(id, productTypeId, symbol, ChargeableCharCategories.Currency, price, AsOf, null, true);

    // ── Global fallback ─────────────────────────────────────────────────────────

    [Fact]
    public async Task GetActiveConfig_NoPerProductType_ReturnsGlobalConfigs()
    {
        _repo.GetActiveForProductTypeAsync(1, AsOf, Arg.Any<CancellationToken>())
            .Returns(new List<ChargeableCharConfig>
            {
                GlobalConfig("$", 1.0m),
                GlobalConfig("%", 0.5m),
            });

        var result = await _service.GetActiveConfigForProductTypeAsync(1, AsOf, CancellationToken.None);

        result.Should().ContainKey("$");
        result["$"].PricePerUnit.Should().Be(1.0m);
        result.Should().ContainKey("%");
    }

    // ── Per-ProductType wins over global ─────────────────────────────────────────

    [Fact]
    public async Task GetActiveConfig_PerProductTypeExists_OverridesGlobalForSameSymbol()
    {
        _repo.GetActiveForProductTypeAsync(5, AsOf, Arg.Any<CancellationToken>())
            .Returns(new List<ChargeableCharConfig>
            {
                GlobalConfig("$", 1.0m),                            // global price = 1.0
                ProductTypeConfig(20L, 5, "$", 3.0m),               // per-PT price = 3.0 → wins
                GlobalConfig("%", 0.5m),                            // global only
            });

        var result = await _service.GetActiveConfigForProductTypeAsync(5, AsOf, CancellationToken.None);

        result["$"].PricePerUnit.Should().Be(3.0m);           // per-PT wins
        result["%"].PricePerUnit.Should().Be(0.5m);           // global only
    }

    [Fact]
    public async Task GetActiveConfig_PerProductTypeExists_IncludesCorrectCategory()
    {
        _repo.GetActiveForProductTypeAsync(5, AsOf, Arg.Any<CancellationToken>())
            .Returns(new List<ChargeableCharConfig>
            {
                ProductTypeConfig(20L, 5, "$", 3.0m),
            });

        var result = await _service.GetActiveConfigForProductTypeAsync(5, AsOf, CancellationToken.None);

        result["$"].Category.Should().Be(ChargeableCharCategories.Currency);
    }

    // ── Empty result ─────────────────────────────────────────────────────────────

    [Fact]
    public async Task GetActiveConfig_NoConfigAtAll_ReturnsEmptyDictionary()
    {
        _repo.GetActiveForProductTypeAsync(1, AsOf, Arg.Any<CancellationToken>())
            .Returns(new List<ChargeableCharConfig>());

        var result = await _service.GetActiveConfigForProductTypeAsync(1, AsOf, CancellationToken.None);

        result.Should().BeEmpty();
    }

    // ── Key: Symbol, Value: snapshot ─────────────────────────────────────────────

    [Fact]
    public async Task GetActiveConfig_KeyIsSymbol()
    {
        _repo.GetActiveForProductTypeAsync(1, AsOf, Arg.Any<CancellationToken>())
            .Returns(new List<ChargeableCharConfig>
            {
                GlobalConfig("!", 2.0m),
            });

        var result = await _service.GetActiveConfigForProductTypeAsync(1, AsOf, CancellationToken.None);

        result.Should().ContainKey("!");
        result["!"].PricePerUnit.Should().Be(2.0m);
    }
}
