package cli

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"

	"github.com/cosmos/cosmos-sdk/client/context"
	"github.com/cosmos/cosmos-sdk/client/flags"
	"github.com/cosmos/cosmos-sdk/codec"
	sdk "github.com/cosmos/cosmos-sdk/types"

	"github.com/kava-labs/kava/x/auction/client/common"
	"github.com/kava-labs/kava/x/auction/types"
)

// Query auction flags
const (
	flagType  = "type"
	flagDenom = "denom"
	flagPhase = "phase"
	flagOwner = "owner"
)

// GetQueryCmd returns the cli query commands for this module
func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
	// Group nameservice queries under a subcommand
	auctionQueryCmd := &cobra.Command{
		Use:   "auction",
		Short: "Querying commands for the auction module",
	}

	auctionQueryCmd.AddCommand(flags.GetCommands(
		QueryGetAuctionCmd(queryRoute, cdc),
		QueryGetAuctionsCmd(queryRoute, cdc),
		QueryParamsCmd(queryRoute, cdc),
	)...)

	return auctionQueryCmd
}

// QueryGetAuctionCmd queries one auction in the store
func QueryGetAuctionCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
	return &cobra.Command{
		Use:   "auction [auction-id]",
		Short: "get a info about an auction",
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			cliCtx := context.NewCLIContext().WithCodec(cdc)

			// Prepare params for querier
			id, err := strconv.ParseUint(args[0], 10, 64)
			if err != nil {
				return fmt.Errorf("auction-id '%s' not a valid uint", args[0])
			}

			auction, height, err := common.QueryAuctionByID(cliCtx, cdc, queryRoute, id)
			if err != nil {
				return err
			}

			auctionWithPhase := types.NewAuctionWithPhase(auction)

			cliCtx = cliCtx.WithHeight(height)
			return cliCtx.PrintOutput(auctionWithPhase)
		},
	}
}

// QueryGetAuctionsCmd queries the auctions in the store
func QueryGetAuctionsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "auctions",
		Short: "query auctions with optional filters",
		Long: strings.TrimSpace(`Query for all paginated auctions that match optional filters:
Example:
$ kvcli q auction auctions --type=(collateral|surplus|debt)
$ kvcli q auction auctions --owner=kava1hatdq32u5x4wnxrtv5wzjzmq49sxgjgsj0mffm
$ kvcli q auction auctions --denom=bnb
$ kvcli q auction auctions --phase=(forward|reverse)
$ kvcli q auction auctions --page=2 --limit=100
`,
		),
		RunE: func(cmd *cobra.Command, args []string) error {
			strType := viper.GetString(flagType)
			strOwner := viper.GetString(flagOwner)
			strDenom := viper.GetString(flagDenom)
			strPhase := viper.GetString(flagPhase)
			page := viper.GetInt(flags.FlagPage)
			limit := viper.GetInt(flags.FlagLimit)

			var (
				auctionType  string
				auctionOwner sdk.AccAddress
				auctionDenom string
				auctionPhase string
			)

			params := types.NewQueryAllAuctionParams(page, limit, auctionType, auctionDenom, auctionPhase, auctionOwner)

			if len(strType) != 0 {
				auctionType = strings.ToLower(strings.TrimSpace(strType))
				if auctionType != types.CollateralAuctionType &&
					auctionType != types.SurplusAuctionType &&
					auctionType != types.DebtAuctionType {
					return fmt.Errorf("invalid auction type %s", strType)
				}
				params.Type = auctionType
			}

			if len(auctionOwner) != 0 {
				if auctionType != types.CollateralAuctionType {
					return fmt.Errorf("cannot apply owner flag to non-collateral auction type")
				}
				auctionOwnerStr := strings.ToLower(strings.TrimSpace(strOwner))
				auctionOwner, err := sdk.AccAddressFromBech32(auctionOwnerStr)
				if err != nil {
					return fmt.Errorf("cannot parse address from auction owner %s", auctionOwnerStr)
				}
				params.Owner = auctionOwner
			}

			if len(strDenom) != 0 {
				auctionDenom := strings.TrimSpace(strDenom)
				err := sdk.ValidateDenom(auctionDenom)
				if err != nil {
					return err
				}
				params.Denom = auctionDenom
			}

			if len(strPhase) != 0 {
				auctionPhase := strings.ToLower(strings.TrimSpace(strPhase))
				if auctionType != types.CollateralAuctionType && len(auctionType) > 0 {
					return fmt.Errorf("cannot apply phase flag to non-collateral auction type")
				}
				if auctionPhase != types.ForwardAuctionPhase && auctionPhase != types.ReverseAuctionPhase {
					return fmt.Errorf("invalid auction phase %s", strPhase)
				}
				params.Phase = auctionPhase
			}

			bz, err := cdc.MarshalJSON(params)
			if err != nil {
				return err
			}

			cliCtx := context.NewCLIContext().WithCodec(cdc)

			// Query
			res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetAuctions), bz)
			if err != nil {
				return err
			}

			// Decode and print results
			var matchingAuctions types.Auctions
			cdc.MustUnmarshalJSON(res, &matchingAuctions)

			if len(matchingAuctions) == 0 {
				return fmt.Errorf("No matching auctions found")
			}

			auctionsWithPhase := []types.AuctionWithPhase{} // using empty slice so json returns [] instead of null when there's no auctions
			for _, a := range matchingAuctions {
				auctionsWithPhase = append(auctionsWithPhase, types.NewAuctionWithPhase(a))
			}
			cliCtx = cliCtx.WithHeight(height)
			return cliCtx.PrintOutput(auctionsWithPhase) // nolint:errcheck
		},
	}

	cmd.Flags().Int(flags.FlagPage, 1, "pagination page of auctions to to query for")
	cmd.Flags().Int(flags.FlagLimit, 100, "pagination limit of auctions to query for")
	cmd.Flags().String(flagType, "", "(optional) filter by auction type, type: collateral, debt, surplus")
	cmd.Flags().String(flagOwner, "", "(optional) filter by collateral auction owner")
	cmd.Flags().String(flagDenom, "", "(optional) filter by auction denom")
	cmd.Flags().String(flagPhase, "", "(optional) filter by collateral auction phase, phase: forward/reverse")

	return cmd
}

// QueryParamsCmd queries the auction module parameters
func QueryParamsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
	return &cobra.Command{
		Use:   "params",
		Short: "get the auction module parameters",
		Long:  "Get the current global auction module parameters.",
		Args:  cobra.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			cliCtx := context.NewCLIContext().WithCodec(cdc)

			// Query
			route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetParams)
			res, height, err := cliCtx.QueryWithData(route, nil)
			if err != nil {
				return err
			}

			// Decode and print results
			var out types.Params
			cdc.MustUnmarshalJSON(res, &out)
			cliCtx = cliCtx.WithHeight(height)
			return cliCtx.PrintOutput(out)
		},
	}
}