package rpc

import (
	"context"
	"encoding/hex"
	"fmt"
	"time"

	cmt "git.cw.tr/mukan-network/mukan-consensus/proto/tendermint/types"
	coretypes "git.cw.tr/mukan-network/mukan-consensus/rpc/core/types"

	"git.cw.tr/mukan-network/mukan-sdk/client"
	sdk "git.cw.tr/mukan-network/mukan-sdk/types"
)

// GetChainHeight returns the current blockchain height.
func GetChainHeight(clientCtx client.Context) (int64, error) {
	node, err := clientCtx.GetNode()
	if err != nil {
		return -1, err
	}

	status, err := node.Status(context.Background())
	if err != nil {
		return -1, err
	}

	height := status.SyncInfo.LatestBlockHeight
	return height, nil
}

// QueryBlocks performs a search for blocks based on BeginBlock and EndBlock
// events via the CometBFT RPC. A custom query may be passed as described below:
//
// To tell which events you want, you need to provide a query. query is a
// string, which has a form: "condition AND condition ..." (no OR at the
// 	moment). condition has a form: "key operation operand". key is a string with
// 	a restricted set of possible symbols ( \t\n\r\\()"'=>< are not allowed).
// 	operation can be "=", "<", "<=", ">", ">=", "CONTAINS" AND "EXISTS". operand
// 	can be a string (escaped with single quotes), number, date or time.

//	Examples:
//		  tm.event = 'NewBlock'               # new blocks
//		  tm.event = 'CompleteProposal'       # node got a complete proposal
//		  tm.event = 'Tx' AND tx.hash = 'XYZ' # single transaction
//		  tm.event = 'Tx' AND tx.height = 5   # all txs of the fifth block
//		  tx.height = 5                       # all txs of the fifth block
//
// For more information, see the /subscribe CometBFT RPC endpoint documentation
func QueryBlocks(clientCtx client.Context, page, limit int, query, orderBy string) (*sdk.SearchBlocksResult, error) {
	node, err := clientCtx.GetNode()
	if err != nil {
		return nil, err
	}

	resBlocks, err := node.BlockSearch(context.Background(), query, &page, &limit, orderBy)
	if err != nil {
		return nil, err
	}

	blocks, err := formatBlockResults(resBlocks.Blocks)
	if err != nil {
		return nil, err
	}

	result := sdk.NewSearchBlocksResult(int64(resBlocks.TotalCount), int64(len(blocks)), int64(page), int64(limit), blocks)

	return result, nil
}

// get block by height
func GetBlockByHeight(clientCtx client.Context, height *int64) (*cmt.Block, error) {
	// get the node
	node, err := clientCtx.GetNode()
	if err != nil {
		return nil, err
	}

	// header -> BlockchainInfo
	// header, tx -> Block
	// results -> BlockResults
	resBlock, err := node.Block(context.Background(), height)
	if err != nil {
		return nil, err
	}

	out := sdk.NewResponseResultBlock(resBlock, resBlock.Block.Time.Format(time.RFC3339))
	if out == nil {
		return nil, fmt.Errorf("unable to create response block from comet result block: %v", resBlock)
	}

	return out, nil
}

func GetBlockByHash(clientCtx client.Context, hashHexString string) (*cmt.Block, error) {
	hash, err := hex.DecodeString(hashHexString)
	if err != nil {
		return nil, err
	}

	// get the node
	node, err := clientCtx.GetNode()
	if err != nil {
		return nil, err
	}

	resBlock, err := node.BlockByHash(context.Background(), hash)

	if err != nil {
		return nil, err
	} else if resBlock.Block == nil {
		return nil, fmt.Errorf("block not found with hash: %s", hashHexString)
	}

	out := sdk.NewResponseResultBlock(resBlock, resBlock.Block.Time.Format(time.RFC3339))
	if out == nil {
		return nil, fmt.Errorf("unable to create response block from comet result block: %v", resBlock)
	}

	return out, nil
}

// formatBlockResults parses the indexed blocks into a slice of BlockResponse objects.
func formatBlockResults(resBlocks []*coretypes.ResultBlock) ([]*cmt.Block, error) {
	out := make([]*cmt.Block, len(resBlocks))
	for i := range resBlocks {
		out[i] = sdk.NewResponseResultBlock(resBlocks[i], resBlocks[i].Block.Time.Format(time.RFC3339))
		if out[i] == nil {
			return nil, fmt.Errorf("unable to create response block from comet result block: %v", resBlocks[i])
		}
	}

	return out, nil
}
