package keeper_test

import (
	"testing"
	"time"

	"github.com/cosmos/cosmos-sdk/codec"
	sdk "github.com/cosmos/cosmos-sdk/types"
	govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
	paramstypes "github.com/cosmos/cosmos-sdk/x/params/types"
	"github.com/stretchr/testify/suite"
	abci "github.com/tendermint/tendermint/abci/types"

	"github.com/kava-labs/kava/app"
	bep3types "github.com/kava-labs/kava/x/bep3/types"
	cdptypes "github.com/kava-labs/kava/x/cdp/types"
	"github.com/kava-labs/kava/x/committee/types"
	pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
)

type PermissionTestSuite struct {
	suite.Suite
	cdc *codec.Codec
}

func (suite *PermissionTestSuite) SetupTest() {
	app := app.NewTestApp()
	suite.cdc = app.Codec()
}

func (suite *PermissionTestSuite) TestSubParamChangePermission_Allows() {
	// cdp CollateralParams
	testCPs := cdptypes.CollateralParams{
		{
			Denom:               "bnb",
			Type:                "bnb-a",
			LiquidationRatio:    d("2.0"),
			DebtLimit:           c("usdx", 1000000000000),
			StabilityFee:        d("1.000000001547125958"),
			LiquidationPenalty:  d("0.05"),
			AuctionSize:         i(100),
			Prefix:              0x20,
			ConversionFactor:    i(6),
			SpotMarketID:        "bnb:usd",
			LiquidationMarketID: "bnb:usd",
		},
		{
			Denom:               "btc",
			Type:                "btc-a",
			LiquidationRatio:    d("1.5"),
			DebtLimit:           c("usdx", 1000000000),
			StabilityFee:        d("1.000000001547125958"),
			LiquidationPenalty:  d("0.10"),
			AuctionSize:         i(1000),
			Prefix:              0x30,
			ConversionFactor:    i(8),
			SpotMarketID:        "btc:usd",
			LiquidationMarketID: "btc:usd",
		},
	}
	testCPUpdatedDebtLimit := make(cdptypes.CollateralParams, len(testCPs))
	copy(testCPUpdatedDebtLimit, testCPs)
	testCPUpdatedDebtLimit[0].DebtLimit = c("usdx", 5000000)

	// cdp DebtParam
	testDP := cdptypes.DebtParam{
		Denom:            "usdx",
		ReferenceAsset:   "usd",
		ConversionFactor: i(6),
		DebtFloor:        i(10000000),
	}
	testDPUpdatedDebtFloor := testDP
	testDPUpdatedDebtFloor.DebtFloor = i(1000)

	// cdp Genesis
	testCDPParams := cdptypes.DefaultParams()
	testCDPParams.CollateralParams = testCPs
	testCDPParams.DebtParam = testDP
	testCDPParams.GlobalDebtLimit = testCPs[0].DebtLimit.Add(testCPs[0].DebtLimit) // correct global debt limit to pass genesis validation

	testDeputy, err := sdk.AccAddressFromBech32("kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj")
	suite.Require().NoError(err)
	// bep3 Asset Params
	testAPs := bep3types.AssetParams{
		bep3types.AssetParam{
			Denom:  "bnb",
			CoinID: 714,
			SupplyLimit: bep3types.SupplyLimit{
				Limit:          sdk.NewInt(350000000000000),
				TimeLimited:    false,
				TimeBasedLimit: sdk.ZeroInt(),
				TimePeriod:     time.Hour,
			},
			Active:        true,
			DeputyAddress: testDeputy,
			FixedFee:      sdk.NewInt(1000),
			MinSwapAmount: sdk.OneInt(),
			MaxSwapAmount: sdk.NewInt(1000000000000),
			MinBlockLock:  bep3types.DefaultMinBlockLock,
			MaxBlockLock:  bep3types.DefaultMaxBlockLock,
		},
		bep3types.AssetParam{
			Denom:  "inc",
			CoinID: 9999,
			SupplyLimit: bep3types.SupplyLimit{
				Limit:          sdk.NewInt(100000000000000),
				TimeLimited:    true,
				TimeBasedLimit: sdk.NewInt(50000000000),
				TimePeriod:     time.Hour,
			},
			Active:        false,
			DeputyAddress: testDeputy,
			FixedFee:      sdk.NewInt(1000),
			MinSwapAmount: sdk.OneInt(),
			MaxSwapAmount: sdk.NewInt(1000000000000),
			MinBlockLock:  bep3types.DefaultMinBlockLock,
			MaxBlockLock:  bep3types.DefaultMaxBlockLock,
		},
	}
	testAPsUpdatedActive := make(bep3types.AssetParams, len(testAPs))
	copy(testAPsUpdatedActive, testAPs)
	testAPsUpdatedActive[1].Active = true

	// bep3 Genesis
	testBep3Params := bep3types.DefaultParams()
	testBep3Params.AssetParams = testAPs

	// pricefeed Markets
	testMs := pricefeedtypes.Markets{
		{
			MarketID:   "bnb:usd",
			BaseAsset:  "bnb",
			QuoteAsset: "usd",
			Oracles:    []sdk.AccAddress{},
			Active:     true,
		},
		{
			MarketID:   "btc:usd",
			BaseAsset:  "btc",
			QuoteAsset: "usd",
			Oracles:    []sdk.AccAddress{},
			Active:     true,
		},
	}
	testMsUpdatedActive := make(pricefeedtypes.Markets, len(testMs))
	copy(testMsUpdatedActive, testMs)
	testMsUpdatedActive[1].Active = true

	testcases := []struct {
		name          string
		genState      []app.GenesisState
		permission    types.SubParamChangePermission
		pubProposal   types.PubProposal
		expectAllowed bool
	}{
		{
			name: "normal",
			genState: []app.GenesisState{
				newPricefeedGenState([]string{"bnb", "btc"}, []sdk.Dec{d("15.01"), d("9500")}),
				newCDPGenesisState(testCDPParams),
				newBep3GenesisState(testBep3Params),
			},
			permission: types.SubParamChangePermission{
				AllowedParams: types.AllowedParams{
					{Subspace: cdptypes.ModuleName, Key: string(cdptypes.KeyDebtThreshold)},
					{Subspace: cdptypes.ModuleName, Key: string(cdptypes.KeyCollateralParams)},
					{Subspace: cdptypes.ModuleName, Key: string(cdptypes.KeyDebtParam)},
					{Subspace: bep3types.ModuleName, Key: string(bep3types.KeyAssetParams)},
					{Subspace: pricefeedtypes.ModuleName, Key: string(pricefeedtypes.KeyMarkets)},
				},
				AllowedCollateralParams: types.AllowedCollateralParams{
					{
						Type:         "bnb-a",
						DebtLimit:    true,
						StabilityFee: true,
					},
					{ // TODO currently even if a perm doesn't allow a change in one element it must still be present in list
						Type: "btc-a",
					},
				},
				AllowedDebtParam: types.AllowedDebtParam{
					DebtFloor: true,
				},
				AllowedAssetParams: types.AllowedAssetParams{
					{
						Denom: "bnb",
					},
					{
						Denom:  "inc",
						Active: true,
					},
				},
				AllowedMarkets: types.AllowedMarkets{
					{
						MarketID: "bnb:usd",
					},
					{
						MarketID: "btc:usd",
						Active:   true,
					},
				},
			},
			pubProposal: paramstypes.NewParameterChangeProposal(
				"A Title",
				"A description for this proposal.",
				[]paramstypes.ParamChange{
					{
						Subspace: cdptypes.ModuleName,
						Key:      string(cdptypes.KeyDebtThreshold),
						Value:    string(suite.cdc.MustMarshalJSON(i(1234))),
					},
					{
						Subspace: cdptypes.ModuleName,
						Key:      string(cdptypes.KeyCollateralParams),
						Value:    string(suite.cdc.MustMarshalJSON(testCPUpdatedDebtLimit)),
					},
					{
						Subspace: cdptypes.ModuleName,
						Key:      string(cdptypes.KeyDebtParam),
						Value:    string(suite.cdc.MustMarshalJSON(testDPUpdatedDebtFloor)),
					},
					{
						Subspace: bep3types.ModuleName,
						Key:      string(bep3types.KeyAssetParams),
						Value:    string(suite.cdc.MustMarshalJSON(testAPsUpdatedActive)),
					},
					{
						Subspace: pricefeedtypes.ModuleName,
						Key:      string(pricefeedtypes.KeyMarkets),
						Value:    string(suite.cdc.MustMarshalJSON(testMsUpdatedActive)),
					},
				},
			),
			expectAllowed: true,
		},
		{
			name:          "not allowed (wrong pubproposal type)",
			permission:    types.SubParamChangePermission{},
			pubProposal:   govtypes.NewTextProposal("A Title", "A description for this proposal."),
			expectAllowed: false,
		},
		{
			name:          "not allowed (nil pubproposal)",
			permission:    types.SubParamChangePermission{},
			pubProposal:   nil,
			expectAllowed: false,
		},
		// TODO more cases
	}

	for _, tc := range testcases {
		suite.Run(tc.name, func() {
			tApp := app.NewTestApp()
			ctx := tApp.NewContext(true, abci.Header{})
			tApp.InitializeFromGenesisStates(tc.genState...)

			suite.Equal(
				tc.expectAllowed,
				tc.permission.Allows(ctx, tApp.Codec(), tApp.GetParamsKeeper(), tc.pubProposal),
			)
		})
	}

}
func TestPermissionTestSuite(t *testing.T) {
	suite.Run(t, new(PermissionTestSuite))
}
