using System;
using PalladiumWallet.Core.Wallet;

namespace PalladiumWallet.Tests.Wallet;

public class CoinAmountTests
{
    // ---- valid amounts: all units ----

    [Theory]
    [InlineData("1",          "sat",  1)]
    [InlineData("0",          "sat",  0)]
    [InlineData("0",          "PLM",  0)]
    [InlineData("0",          "mPLM", 0)]
    [InlineData("0",          "µPLM", 0)]
    [InlineData("1.5",        "PLM",  150_000_000)]
    [InlineData("0.00000001", "PLM",  1)]
    [InlineData("1",          "PLM",  100_000_000)]
    [InlineData("1.00000",    "mPLM", 100_000)]
    [InlineData("0.00001",    "mPLM", 1)]
    [InlineData("1.00",       "µPLM", 100)]
    [InlineData("0.01",       "µPLM", 1)]
    [InlineData("100000000",  "sat",  100_000_000)]
    public void Importo_valido_viene_accettato(string input, string unit, long expectedSats)
    {
        Assert.True(CoinAmount.TryParseIn(input, unit, out var sats));
        Assert.Equal(expectedSats, sats);
    }

    // ---- decimale con virgola (locale italiano) ----

    [Theory]
    [InlineData("1,5",        "PLM",  150_000_000)]
    [InlineData("1,00000",    "mPLM", 100_000)]
    [InlineData("0,00000001", "PLM",  1)]
    public void Virgola_italiana_viene_accettata(string input, string unit, long expectedSats)
    {
        Assert.True(CoinAmount.TryParseIn(input, unit, out var sats));
        Assert.Equal(expectedSats, sats);
    }

    // ---- spazi iniziali/finali ----

    [Theory]
    [InlineData("  1  ", "sat", 1)]
    [InlineData(" 1.5 ", "PLM", 150_000_000)]
    public void Spazi_iniziali_e_finali_vengono_ignorati(string input, string unit, long expectedSats)
    {
        Assert.True(CoinAmount.TryParseIn(input, unit, out var sats));
        Assert.Equal(expectedSats, sats);
    }

    // ---- importi con troppi decimali ----

    [Theory]
    [InlineData("1.9",          "sat")]
    [InlineData("1.1",          "sat")]
    [InlineData("0.001",        "µPLM")]
    [InlineData("1.500000001",  "PLM")]
    [InlineData("0.000000001",  "PLM")]
    [InlineData("0.000001",     "mPLM")]
    public void Importo_con_troppi_decimali_viene_rifiutato(string input, string unit)
    {
        Assert.False(CoinAmount.TryParseIn(input, unit, out _));
    }

    // ---- negativi ----

    [Theory]
    [InlineData("-1",   "sat")]
    [InlineData("-0.1", "PLM")]
    [InlineData("-1",   "PLM")]
    public void Importo_negativo_viene_rifiutato(string input, string unit)
    {
        Assert.False(CoinAmount.TryParseIn(input, unit, out _));
    }

    // ---- stringa vuota e non numerica ----

    [Theory]
    [InlineData("")]
    [InlineData("abc")]
    [InlineData("1e5")]
    [InlineData("∞")]
    public void Stringa_non_numerica_viene_rifiutata(string input)
    {
        Assert.False(CoinAmount.TryParseIn(input, "PLM", out _));
    }

    // ---- overflow ----

    [Fact]
    public void Overflow_viene_rifiutato()
    {
        // 92233720368.54775807 PLM supera long.MaxValue in satoshi
        Assert.False(CoinAmount.TryParseIn("99999999999", "PLM", out _));
    }

    // ---- unknown unit throws ArgumentException ----

    [Theory]
    [InlineData("banana")]
    [InlineData("BTC")]
    [InlineData("")]
    [InlineData("plm")]   // case-sensitive
    public void Unita_sconosciuta_lancia_ArgumentException(string unit)
    {
        Assert.Throws<ArgumentException>(() => CoinAmount.TryParseIn("1", unit, out _));
    }

    [Fact]
    public void FormatIn_unita_sconosciuta_lancia_ArgumentException()
    {
        Assert.Throws<ArgumentException>(() => CoinAmount.FormatIn(1, "banana"));
    }

    // ---- roundtrip FormatIn → TryParseIn ----

    [Theory]
    [InlineData(0,              "PLM")]
    [InlineData(1,              "sat")]
    [InlineData(1,              "PLM")]
    [InlineData(150_000_000,    "PLM")]
    [InlineData(100_000,        "mPLM")]
    [InlineData(100,            "µPLM")]
    [InlineData(99_999_999,     "PLM")]
    public void Roundtrip_format_parse_conserva_i_satoshi(long sats, string unit)
    {
        var formatted = CoinAmount.FormatIn(sats, unit, withLabel: false);
        Assert.True(CoinAmount.TryParseIn(formatted, unit, out var parsed));
        Assert.Equal(sats, parsed);
    }

    // ---- TryParseCoins ----

    [Fact]
    public void TryParseCoins_accetta_precisione_massima()
    {
        Assert.True(CoinAmount.TryParseCoins("0.00000001", out var sats));
        Assert.Equal(1L, sats);
    }

    [Fact]
    public void TryParseCoins_virgola_italiana()
    {
        Assert.True(CoinAmount.TryParseCoins("1,5", out var sats));
        Assert.Equal(150_000_000L, sats);
    }

    [Fact]
    public void TryParseCoins_rifiuta_sotto_al_satoshi()
    {
        Assert.False(CoinAmount.TryParseCoins("0.000000001", out _));
    }

    [Fact]
    public void TryParseCoins_rifiuta_importo_negativo()
    {
        Assert.False(CoinAmount.TryParseCoins("-1", out _));
    }

    [Fact]
    public void TryParseCoins_rifiuta_overflow()
    {
        Assert.False(CoinAmount.TryParseCoins("99999999999", out _));
    }

    // ---- Format (PLM con 8 decimali) ----

    [Theory]
    [InlineData(100_000_000, "1.00000000")]
    [InlineData(1,           "0.00000001")]
    [InlineData(0,           "0.00000000")]
    public void Format_produce_stringa_con_8_decimali(long sats, string expected)
    {
        Assert.Equal(expected, CoinAmount.Format(sats));
    }
}
