package rpc_test

import (
	"context"
	"fmt"
	"strconv"
	"testing"

	abci "git.cw.tr/mukan-network/mukan-consensus/abci/types"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"

	"git.cw.tr/mukan-network/mukan-sdk/testutil/network"
	"git.cw.tr/mukan-network/mukan-sdk/testutil/testdata"
	"git.cw.tr/mukan-network/mukan-sdk/types/address"
	grpctypes "git.cw.tr/mukan-network/mukan-sdk/types/grpc"
	banktypes "git.cw.tr/mukan-network/mukan-sdk/x/bank/types"
)

type IntegrationTestSuite struct {
	suite.Suite

	network *network.Network
}

func (s *IntegrationTestSuite) SetupSuite() {
	s.T().Log("setting up integration test suite")

	cfg, err := network.DefaultConfigWithAppConfig(network.MinimumAppConfig())

	s.NoError(err)

	s.network, err = network.New(s.T(), s.T().TempDir(), cfg)
	s.Require().NoError(err)

	s.Require().NoError(s.network.WaitForNextBlock())
}

func (s *IntegrationTestSuite) TearDownSuite() {
	s.T().Log("tearing down integration test suite")
	s.network.Cleanup()
}

func (s *IntegrationTestSuite) TestCLIQueryConn() {
	s.T().Skip("data race in comet is causing this to fail")
	var header metadata.MD

	testClient := testdata.NewQueryClient(s.network.Validators[0].ClientCtx)
	res, err := testClient.Echo(context.Background(), &testdata.EchoRequest{Message: "hello"}, grpc.Header(&header))
	s.NoError(err)

	blockHeight := header.Get(grpctypes.GRPCBlockHeightHeader)
	height, err := strconv.Atoi(blockHeight[0])
	s.Require().NoError(err)
	s.Require().GreaterOrEqual(height, 1) // at least the 1st block

	s.Equal("hello", res.Message)
}

func (s *IntegrationTestSuite) TestQueryABCIHeight() {
	testCases := []struct {
		name                string
		reqHeight           int64
		ctxHeight           int64
		awaitMinChainHeight int64
		assertFn            func(t *testing.T, latestHeightAtQuery int64, resp abci.ResponseQuery)
	}{
		{
			name:                "request height set",
			reqHeight:           2, // no proof when < 2
			ctxHeight:           1,
			awaitMinChainHeight: 3, // wait +1 block to be on the safe side
			assertFn: func(t *testing.T, _ int64, resp abci.ResponseQuery) {
				t.Helper()
				assert.Equal(t, int64(2), resp.Height)
			},
		},
		{
			name:                "fallback to context height when request height is not set",
			reqHeight:           0,
			ctxHeight:           3,
			awaitMinChainHeight: 4, // wait +1 block to be on the safe side
			assertFn: func(t *testing.T, _ int64, resp abci.ResponseQuery) {
				t.Helper()
				assert.Equal(t, int64(3), resp.Height)
			},
		},
		{
			name:                "with empty values, use latest height",
			reqHeight:           0,
			ctxHeight:           0,
			awaitMinChainHeight: 2, // no proof when < 2
			assertFn: func(t *testing.T, latestHeightAtQuery int64, resp abci.ResponseQuery) {
				t.Helper()
				anyOf := []int64{latestHeightAtQuery, latestHeightAtQuery - 1}
				assert.Contains(t, anyOf, resp.Height)
			},
		},
	}
	for _, tc := range testCases {
		s.Run(tc.name, func() {
			currentHeight, err := s.network.WaitForHeight(tc.awaitMinChainHeight)
			s.Require().NoError(err)

			val := s.network.Validators[0]
			clientCtx := val.ClientCtx
			clientCtx = clientCtx.WithHeight(tc.ctxHeight)

			req := abci.RequestQuery{
				Path:   fmt.Sprintf("store/%s/key", banktypes.StoreKey),
				Height: tc.reqHeight,
				Data:   address.MustLengthPrefix(val.Address),
				Prove:  true,
			}

			res, err := clientCtx.QueryABCI(req)
			s.Require().NoError(err)
			tc.assertFn(s.T(), currentHeight, res)
		})
	}
}

func TestIntegrationTestSuite(t *testing.T) {
	suite.Run(t, new(IntegrationTestSuite))
}
