package keeper

import (
	"context"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/cosmos/cosmos-sdk/store/prefix"
	sdk "github.com/cosmos/cosmos-sdk/types"
	"github.com/cosmos/cosmos-sdk/types/query"

	"github.com/0glabs/0g-chain/x/evmutil/types"
)

type queryServer struct {
	keeper Keeper
}

// NewQueryServerImpl creates a new server for handling gRPC queries.
func NewQueryServerImpl(k Keeper) types.QueryServer {
	return &queryServer{keeper: k}
}

var _ types.QueryServer = queryServer{}

// Params queries module params
func (s queryServer) Params(stdCtx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) {
	if req == nil {
		return nil, status.Errorf(codes.InvalidArgument, "empty request")
	}

	ctx := sdk.UnwrapSDKContext(stdCtx)
	params := s.keeper.GetParams(ctx)

	return &types.QueryParamsResponse{Params: params}, nil
}

// DeployedCosmosCoinContracts gets contract addresses for deployed erc20 contracts
// representing cosmos-sdk coins
func (s queryServer) DeployedCosmosCoinContracts(
	goCtx context.Context,
	req *types.QueryDeployedCosmosCoinContractsRequest,
) (res *types.QueryDeployedCosmosCoinContractsResponse, err error) {
	if req == nil {
		return nil, status.Errorf(codes.InvalidArgument, "empty request")
	}

	ctx := sdk.UnwrapSDKContext(goCtx)
	if len(req.CosmosDenoms) > 0 {
		res, err = getDeployedCosmosCoinContractsByDenoms(&s.keeper, ctx, req.CosmosDenoms)
	} else {
		// requesting no sdk denoms is a request for all denoms
		res, err = getAllDeployedCosmosCoinContractsPage(&s.keeper, ctx, req.Pagination)
	}

	return res, err
}

// getAllDeployedCosmosCoinContractsPage gets a page of deployed contracts (no filtering)
func getAllDeployedCosmosCoinContractsPage(
	k *Keeper, ctx sdk.Context, pagination *query.PageRequest,
) (*types.QueryDeployedCosmosCoinContractsResponse, error) {
	contracts := make([]types.DeployedCosmosCoinContract, 0)
	contractStore := prefix.NewStore(
		ctx.KVStore(k.storeKey),
		types.DeployedCosmosCoinContractKeyPrefix,
	)

	pageRes, err := query.FilteredPaginate(contractStore, pagination,
		func(key []byte, value []byte, accumulate bool) (bool, error) {
			if !accumulate {
				return true, nil
			}
			address := types.BytesToInternalEVMAddress(value)
			contract := types.DeployedCosmosCoinContract{
				CosmosDenom: string(key),
				Address:     &address,
			}
			contracts = append(contracts, contract)
			return true, nil
		})
	if err != nil {
		return &types.QueryDeployedCosmosCoinContractsResponse{}, err
	}

	return &types.QueryDeployedCosmosCoinContractsResponse{
		DeployedCosmosCoinContracts: contracts,
		Pagination:                  pageRes,
	}, nil
}

func getDeployedCosmosCoinContractsByDenoms(
	k *Keeper, ctx sdk.Context, denoms []string,
) (*types.QueryDeployedCosmosCoinContractsResponse, error) {
	if len(denoms) > query.DefaultLimit {
		// forego dealing with pagination by rejecting reqs for >100 denoms
		return nil, status.Errorf(codes.InvalidArgument, "maximum of %d denoms allowed per request", query.DefaultLimit)
	}

	contracts := make([]types.DeployedCosmosCoinContract, 0, len(denoms))
	for _, denom := range denoms {
		address, found := k.GetDeployedCosmosCoinContract(ctx, denom)
		if !found {
			continue
		}
		contracts = append(contracts, types.NewDeployedCosmosCoinContract(denom, address))
	}

	return &types.QueryDeployedCosmosCoinContractsResponse{
		DeployedCosmosCoinContracts: contracts,
	}, nil
}