using System.Numerics;
using Managing.Common;
using Managing.Infrastructure.Evm.Referentials;
using Managing.Infrastructure.Evm.Services;
using Managing.Infrastructure.Evm.Services.Gmx;
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Web3;
using Xunit;

namespace Managing.Infrastructure.Tests;

public class GmxV2ServiceTests
{
    private readonly GmxV2Service _service;
    private readonly Web3 _web3;

    public GmxV2ServiceTests()
    {
        var defaultChain = ChainService.GetArbitrum();
        _web3 = new Web3(defaultChain.RpcUrl);
        _service = new GmxV2Service();
    }

    [Fact]
    public async Task Get_Multiple_Markets_With_MultiCall()
    {
        // Act
        var result = await _service.GetMarketsAsync(_web3);

        // Assert
        Assert.NotNull(result);
        Assert.NotEmpty(result);
        Assert.All(result, r =>
        {
            Assert.NotNull(r.MarketToken);
            Assert.NotNull(r.IndexToken);
            Assert.NotNull(r.LongToken);
            Assert.NotNull(r.ShortToken);
        });
    }

    [Fact]
    public async Task Get_Multiple_Markets()
    {
        var result = await _service.GetMarkets(_web3);
        Assert.NotNull(result);
    }

    [Fact]
    public async Task Get_Multiple_MarketsInfos_With_Multicall()
    {
        // Act
        var result = await _service.GetMarketInfosAsync(_web3);

        // Assert
        Assert.NotNull(result);
        Assert.NotEmpty(result);
    }

    [Fact]
    public async Task Get_Single_MarketInfos()
    {
        var result = await _service.GetMarketInfo(_web3);
        Assert.NotNull(result);
    }

    [Fact]
    public async Task Get_Single_FundingRate()
    {
        var result = await _service.GetFundingRate(_web3);
        Assert.NotNull(result);
    }

    [Fact]
    public async Task Get_Market_Token_Price()
    {
        var result = await _service.GetMarketTokenPrice(_web3);
        var resultMaximized = await _service.GetMarketTokenPrice(_web3, false);

        Assert.NotNull(result);
        Assert.NotNull(resultMaximized);
    }

    [Fact]
    public async Task Get_IsMarket_Disabled()
    {
        var result = await _service.GetIsMarketDisabled(_web3);
        Assert.False(result);
    }

    [Fact]
    public async Task Get_Long_Interest_Amount()
    {
        var result = await _service.GetLongInterestAmount(_web3);
        Assert.NotNull(result);
    }

    [Fact]
    public async Task Get_FundigRates()
    {
        var result = await _service.GetFundingRates(_web3);
        Assert.NotNull(result);
    }

    [Fact]
    public void Should_Return_Correct_Long_Key()
    {
        var expectedKey = "0xaaf79028722fd65633cdf994a4a10b0bd349761062506c4bb6a0489956ba5d3f";
        var keys = GmxKeysService.GetMarketKeys(Constants.GMX.Markets.BTCUSDC);

        Assert.NotNull(keys);
        Assert.Equal(expectedKey, keys.LongInterestUsingLongToken);
    }

    [Fact]
    public void Should_Hash_Correctly_Simple_Key()
    {
        var key = "POSITION_IMPACT_FACTOR";
        var keys = new GmxV2Encoder().HashString(key);
        var expectedKey = "0xdbe66c23ce3688bc34131316a8b4dd967631de35afb1dbb972bc1b7c71c4a18b";

        Assert.NotNull(keys);
        Assert.Equal(expectedKey.HexToByteArray(), keys);
    }

    [Fact]
    public void Should_Hash_Correctly_Complex_Key()
    {
        var helper = new GmxV2Encoder();
        var openInterestKey = "OPEN_INTEREST";
        var hashedOpenInterestKey = helper.HashString(openInterestKey);
        var expectedOpenInterestKey = "0x4259c04608de5df0fa0e375507d66e8ffc81773c9f3dd501a181cbb349ae4fe0";
        Assert.Equal(expectedOpenInterestKey.HexToByteArray(), hashedOpenInterestKey);

        var datas = new List<(string dataType, object dataValues)>();
        datas.Add(("bytes32", hashedOpenInterestKey));
        datas.Add(("address", Constants.GMX.Markets.BTCUSDC));
        datas.Add(("address", Constants.GMX.TokenAddress.WBTC));
        datas.Add(("bool", true));

        var hashData = helper.HashData(datas);
        var expectedFullKey = "0xaaf79028722fd65633cdf994a4a10b0bd349761062506c4bb6a0489956ba5d3f".HexToByteArray();
        Assert.NotNull(hashData);
        Assert.Equal(expectedFullKey, hashData);
    }

    [Fact]
    public async void Should_return_GmxGasLimit()
    {
        var result = await _service.GetGasLimit(_web3);
        Assert.NotNull(result);
    }

    [Theory]
    [InlineData(50200, "503506000000000000000000000", Enums.Ticker.BTC)]
    public void Should_return_correct_acceptable_price(decimal price, string expected, Enums.Ticker ticker)
    {
        var isLong = true;
        var isIncrease = true;
        var acceptablePrice = GmxV2Helpers.GetAcceptablePrice(price, isLong);
        var expectedPrice = BigInteger.Parse(expected);
        Assert.IsType<BigInteger>(acceptablePrice);
        Assert.Equal(expectedPrice, acceptablePrice);
    }

    [Fact]
    public async Task Should_return_execution_fee()
    {
        var executionFee = await _service.GetExecutionFee(_web3, GmxV2Enums.TradeType.Increase);

        var expectedExecutionFee = BigInteger.Parse("56084990000000");
        // No need to check the execution fee since it's dynamic
        // Assert.True(IsCloseTo(expectedExecutionFee, executionFee.FeeAmount, 2000000000000));
        Assert.NotNull(executionFee);
    }

    [Theory]
    [InlineData(19, "18986709")]
    public async Task Should_return_correct_collateral_amount(decimal price, string expected)
    {
        var usdcToken = TokenV2Service.TOKENS.First(t => t.Symbol == Enums.Ticker.USDC.ToString());
        var initialCollateralAmount = GmxV2Helpers.ParseValue(price, usdcToken.Decimals);
        var expectedAmount = BigInteger.Parse(expected);
        var executionFee = await _service.GetExecutionFee(_web3, GmxV2Enums.TradeType.Increase);

        var amountToIncrease = initialCollateralAmount + executionFee.FeeAmount;

        var expectedExecutionFee = BigInteger.Parse("56084990000000");
        // No need to check the execution fee since it's dynamic
        // Assert.True(IsCloseTo(expectedExecutionFee, executionFee.FeeAmount, 2000000000000));


        Assert.IsType<BigInteger>(amountToIncrease);
        Assert.True(IsCloseTo(expectedAmount, amountToIncrease, 2000000));
    }

    private bool IsCloseTo(BigInteger expected, BigInteger actual, BigInteger tolerance)
    {
        var diff = BigInteger.Abs(expected - actual);
        return diff < tolerance;
    }

    [Fact]
    public void Should_return_correct_delta_usd_amount()
    {
        var expected = "53954097244701048554400000000000";
        var input = 54;
        var usdcToken = TokenV2Service.TOKENS.First(t => t.Symbol == Enums.Ticker.USDC.ToString());
        var deltaUsdAmount = GmxV2Helpers.ParseValue(input, 30);

        Assert.True(IsCloseTo(BigInteger.Parse(expected),
            deltaUsdAmount,
            BigInteger.Parse("1395409724470104855440000000000")));
    }

    [Fact]
    public void Should_return_correct_trigger_price()
    {
        var expected = "589010000000000000000000000";
        var price = 58901;
        var token = TokenV2Service.TOKENS.First(t => t.Symbol == Enums.Ticker.BTC.ToString());

        var triggerPrice = GmxV2Helpers.ConvertToContractPrice(price, token.Decimals, true);
        Assert.Equal(BigInteger.Parse(expected),
            triggerPrice);
    }


    [Fact]
    public async void Should_return_oraclePrices()
    {
        var service = new OracleKeeperService();
        var result = await service.GetTickers();
        Assert.NotNull(result);
    }
}