package cosmos

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	sdk "github.com/cosmos/cosmos-sdk/types"
	"github.com/deltaswapio/swaprouter/v3/common"
	"github.com/deltaswapio/swaprouter/v3/log"
	"github.com/deltaswapio/swaprouter/v3/rpc/client"
	"github.com/deltaswapio/swaprouter/v3/tokens"
)

const (
	LatestBlock = "/cosmos/base/tendermint/v1beta1/blocks/latest"
	TxByHash    = "/cosmos/tx/v1beta1/txs/"
	AccountInfo = "/cosmos/auth/v1beta1/accounts/"
	Balances    = "/cosmos/bank/v1beta1/balances/"
	SimulateTx  = "/cosmos/tx/v1beta1/simulate"
	BroadTx     = "/cosmos/tx/v1beta1/txs"
)

var wrapRPCQueryError = tokens.WrapRPCQueryError

func joinURLPath(url, path string) string {
	url = strings.TrimSuffix(url, "/")
	if !strings.HasPrefix(path, "/") {
		url += "/"
	}
	return url + path
}

func (b *Bridge) GetLatestBlockNumber() (uint64, error) {
	if result, err := b.GRPCGetLatestBlockNumber(); err == nil {
		return result, nil
	} else if len(b.GatewayConfig.AllGatewayURLs) == 0 {
		return 0, err
	}
	var result *GetLatestBlockResponse
	var err error
	for _, url := range b.GatewayConfig.AllGatewayURLs {
		restApi := joinURLPath(url, LatestBlock)
		if err = client.RPCGet(&result, restApi); err == nil {
			if height, err := strconv.ParseUint(result.Block.Header.Height, 10, 64); err == nil {
				return height, nil
			}
		}
	}
	return 0, wrapRPCQueryError(err, "GetLatestBlockNumber")
}

func (b *Bridge) GetLatestBlockNumberOf(apiAddress string) (uint64, error) {
	if _, exist := rpcClientsMap[apiAddress]; exist {
		if result, err := b.GRPCGetLatestBlockNumberOf(apiAddress); err == nil {
			return result, nil
		} else if len(b.GatewayConfig.AllGatewayURLs) == 0 {
			return 0, err
		}
	}
	var result *GetLatestBlockResponse
	restApi := joinURLPath(apiAddress, LatestBlock)
	if err := client.RPCGet(&result, restApi); err == nil {
		return strconv.ParseUint(result.Block.Header.Height, 10, 64)
	} else {
		return 0, wrapRPCQueryError(err, "GetLatestBlockNumber")
	}
}

func (b *Bridge) GetChainID() (string, error) {
	if result, err := b.GRPCGetChainID(); err == nil {
		return result, nil
	} else if len(b.GatewayConfig.AllGatewayURLs) == 0 {
		return "", err
	}
	var result *GetLatestBlockResponse
	var err error
	for _, url := range b.GatewayConfig.AllGatewayURLs {
		restApi := joinURLPath(url, LatestBlock)
		if err = client.RPCGet(&result, restApi); err == nil {
			return result.Block.Header.ChainID, nil
		}
	}
	return "", wrapRPCQueryError(err, "GetChainID")
}

func (b *Bridge) GetTransactionByHash(txHash string) (*GetTxResponse, error) {
	if result, err := b.GRPCGetTransactionByHash(txHash); err == nil {
		return result, nil
	} else if len(b.GatewayConfig.AllGatewayURLs) == 0 {
		return nil, err
	}
	var result *GetTxResponse
	var err error
	for _, url := range b.GatewayConfig.AllGatewayURLs {
		restApi := joinURLPath(url, TxByHash+txHash)
		if err = client.RPCGet(&result, restApi); err == nil {
			return result, nil
		}
	}
	return nil, wrapRPCQueryError(err, "GetTransactionByHash")
}

func (b *Bridge) GetBaseAccount(address string) (*QueryAccountResponse, error) {
	if result, err := b.GRPCGetBaseAccount(address); err == nil {
		return result, nil
	} else if len(b.GatewayConfig.AllGatewayURLs) == 0 {
		return nil, err
	}
	var result *QueryAccountResponse
	var err error
	for _, url := range b.GatewayConfig.AllGatewayURLs {
		restApi := joinURLPath(url, AccountInfo+address)
		if err = client.RPCGet(&result, restApi); err == nil {
			return result, nil
		} else {
			log.Warn("GetBaseAccount failed", "url", restApi, "err", err)
		}
	}
	return nil, wrapRPCQueryError(err, "GetBaseAccount")
}

func (b *Bridge) GetDenomBalance(address, denom string) (sdk.Int, error) {
	if result, err := b.GRPCGetDenomBalance(address, denom); err == nil {
		return result, nil
	} else if len(b.GatewayConfig.AllGatewayURLs) == 0 {
		return sdk.ZeroInt(), err
	}
	var result *QueryAllBalancesResponse
	var err error
	for _, url := range b.GatewayConfig.AllGatewayURLs {
		restApi := joinURLPath(url, Balances+address)
		if err = client.RPCGet(&result, restApi); err == nil {
			for _, coin := range result.Balances {
				if coin.Denom == denom {
					return coin.Amount, nil
				}
			}
			return sdk.ZeroInt(), nil
		} else {
			log.Warn("GetDenomBalance failed", "url", restApi, "err", err)
		}
	}
	return sdk.ZeroInt(), wrapRPCQueryError(err, "GetDenomBalance")
}

func (b *Bridge) SimulateTx(simulateReq *SimulateRequest) (string, error) {
	if result, err := b.GRPCSimulateTx(simulateReq); err == nil {
		return common.ToJSONString(result.GasInfo, false), nil
	} else if len(b.GatewayConfig.AllGatewayURLs) == 0 {
		return "", err
	}
	if data, err := json.Marshal(simulateReq); err != nil {
		return "", err
	} else {
		for _, url := range b.GatewayConfig.AllGatewayURLs {
			restApi := joinURLPath(url, SimulateTx)
			if res, err := client.RPCRawPostWithTimeout(restApi, string(data), 120); err == nil && res != "" && res != "\n" {
				return res, nil
			}
		}
		return "", wrapRPCQueryError(err, "SimulateTx")
	}
}

func (b *Bridge) BroadcastTx(req *BroadcastTxRequest) (string, error) {
	if result, err := b.GRPCBroadcastTx(req); err == nil {
		data, _ := json.Marshal(BroadcastTxResponse{
			TxResponse: &TxResponse{
				Height: fmt.Sprintf("%d", result.Height),
				TxHash: result.TxHash,
				Code:   result.Code,
				Logs:   result.Logs,
			},
		})
		return string(data), nil
	} else if len(b.GatewayConfig.AllGatewayURLs) == 0 {
		return "", err
	}
	if data, err := json.Marshal(req); err != nil {
		return "", err
	} else {
		var res string
		var success bool
		for _, url := range b.GatewayConfig.AllGatewayURLs {
			restApi := joinURLPath(url, BroadTx)
			if res, err = client.RPCJsonPostWithTimeout(restApi, string(data), 120); err == nil {
				success = true
			}
		}
		if success {
			return res, nil
		}
		return "", wrapRPCQueryError(err, "BroadcastTx")
	}
}
