using NBitcoin;
using PalladiumWallet.Core.Chain;
using PalladiumWallet.Core.Crypto;

namespace PalladiumWallet.Tests.Crypto;

public class AddressDerivationTests
{
    private static byte[] AbandonAboutSeed()
    {
        Assert.True(Bip39.TryParse(
            "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
            out var mnemonic));
        return Bip39.ToSeed(mnemonic!);
    }

    [Fact]
    public void Il_vettore_bip44_produce_lo_stesso_hash_su_bitcoin_e_plm()
    {
        // Known abandon-about address at m/44'/0'/0'/0/0 (public reference).
        var account = HdAccount.FromSeed(AbandonAboutSeed(), ScriptKind.Legacy,
            ChainProfiles.Mainnet, new KeyPath("44'/0'/0'"));
        var pubKey = account.GetPublicKey(isChange: false, 0)!;

        var bitcoinAddr = pubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main);
        Assert.Equal("1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA", bitcoinAddr.ToString());

        var plmAddr = account.GetReceiveAddress(0);
        Assert.StartsWith("P", plmAddr.ToString());
        // Same hash160 under the two prefixes: the derivation is identical,
        // only the network dress changes.
        Assert.Equal(pubKey.Hash, ((BitcoinPubKeyAddress)plmAddr).Hash);
    }

    [Fact]
    public void Il_vettore_bip49_produce_lo_stesso_script_hash_su_bitcoin_e_plm()
    {
        var account = HdAccount.FromSeed(AbandonAboutSeed(), ScriptKind.WrappedSegwit,
            ChainProfiles.Mainnet, new KeyPath("49'/0'/0'"));
        var pubKey = account.GetPublicKey(isChange: false, 0)!;

        var bitcoinAddr = pubKey.GetAddress(ScriptPubKeyType.SegwitP2SH, Network.Main);
        Assert.Equal("37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf", bitcoinAddr.ToString());

        var plmAddr = account.GetReceiveAddress(0);
        Assert.StartsWith("3", plmAddr.ToString());
        Assert.Equal(((BitcoinScriptAddress)bitcoinAddr).Hash, ((BitcoinScriptAddress)plmAddr).Hash);
    }

    [Theory]
    [InlineData(ScriptKind.Legacy, NetKind.Mainnet)]
    [InlineData(ScriptKind.WrappedSegwit, NetKind.Mainnet)]
    [InlineData(ScriptKind.NativeSegwit, NetKind.Mainnet)]
    [InlineData(ScriptKind.Legacy, NetKind.Testnet)]
    [InlineData(ScriptKind.WrappedSegwit, NetKind.Testnet)]
    [InlineData(ScriptKind.NativeSegwit, NetKind.Testnet)]
    [InlineData(ScriptKind.NativeSegwit, NetKind.Regtest)]
    public void Ogni_indirizzo_derivato_fa_roundtrip_sulla_propria_rete(ScriptKind kind, NetKind net)
    {
        var profile = ChainProfiles.For(net);
        var account = HdAccount.FromSeed(AbandonAboutSeed(), kind, profile);

        var addr = account.GetReceiveAddress(0);
        var parsed = BitcoinAddress.Create(addr.ToString(), PalladiumNetworks.For(net));

        Assert.Equal(addr.ScriptPubKey, parsed.ScriptPubKey);
    }

    [Theory]
    [InlineData(NetKind.Mainnet, "tplm1")]
    [InlineData(NetKind.Mainnet, "rplm1")]
    public void Gli_indirizzi_segwit_mainnet_non_hanno_prefissi_di_altre_reti(NetKind net, string wrongPrefix)
    {
        var account = HdAccount.FromSeed(AbandonAboutSeed(), ScriptKind.NativeSegwit, ChainProfiles.For(net));
        Assert.DoesNotContain(wrongPrefix, account.GetReceiveAddress(0).ToString());
    }

    [Fact]
    public void Il_path_di_account_usa_il_coin_type_del_profilo()
    {
        Assert.Equal("84'/746'/0'",
            DerivationPaths.AccountPath(ScriptKind.NativeSegwit, ChainProfiles.Mainnet).ToString());
        Assert.Equal("84'/1'/0'",
            DerivationPaths.AccountPath(ScriptKind.NativeSegwit, ChainProfiles.Testnet).ToString());
        Assert.Equal("44'/746'/2'",
            DerivationPaths.AccountPath(ScriptKind.Legacy, ChainProfiles.Mainnet, account: 2).ToString());
    }

    [Fact]
    public void Receiving_e_change_derivano_indirizzi_diversi()
    {
        var account = HdAccount.FromSeed(AbandonAboutSeed(), ScriptKind.NativeSegwit, ChainProfiles.Mainnet);
        Assert.NotEqual(account.GetReceiveAddress(0).ToString(), account.GetChangeAddress(0).ToString());
        Assert.NotEqual(account.GetReceiveAddress(0).ToString(), account.GetReceiveAddress(1).ToString());
    }
}
