package cli

import (
	"context"
	"encoding/hex"
	"fmt"
	"strconv"
	"strings"

	"github.com/cosmos/cosmos-sdk/client"
	"github.com/cosmos/cosmos-sdk/client/flags"
	sdk "github.com/cosmos/cosmos-sdk/types"
	"github.com/spf13/cobra"
	tmtime "github.com/tendermint/tendermint/types/time"

	"github.com/kava-labs/kava/x/bep3/types"
)

// Query atomic swaps flags
const (
	flagInvolve    = "involve"
	flagExpiration = "expiration"
	flagStatus     = "status"
	flagDirection  = "direction"
)

// GetQueryCmd returns the cli query commands for this module
func GetQueryCmd(queryRoute string) *cobra.Command {
	// Group bep3 queries under a subcommand
	bep3QueryCmd := &cobra.Command{
		Use:                        "bep3",
		Short:                      "Querying commands for the bep3 module",
		DisableFlagParsing:         true,
		SuggestionsMinimumDistance: 2,
		RunE:                       client.ValidateCmd,
	}

	cmds := []*cobra.Command{
		QueryCalcSwapIDCmd(queryRoute),
		QueryCalcRandomNumberHashCmd(queryRoute),
		QueryGetAssetSupplyCmd(queryRoute),
		QueryGetAssetSuppliesCmd(queryRoute),
		QueryGetAtomicSwapCmd(queryRoute),
		QueryGetAtomicSwapsCmd(queryRoute),
		QueryParamsCmd(queryRoute),
	}

	for _, cmd := range cmds {
		flags.AddQueryFlagsToCmd(cmd)
	}

	bep3QueryCmd.AddCommand(cmds...)

	return bep3QueryCmd
}

// QueryCalcRandomNumberHashCmd calculates the random number hash for a number and timestamp
func QueryCalcRandomNumberHashCmd(queryRoute string) *cobra.Command {
	return &cobra.Command{
		Use:     "calc-rnh [unix-timestamp]",
		Short:   "calculates an example random number hash from an optional timestamp",
		Example: "bep3 calc-rnh now",
		Args:    cobra.MaximumNArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			clientCtx, err := client.GetClientQueryContext(cmd)
			if err != nil {
				return err
			}

			userTimestamp := "now"
			if len(args) > 0 {
				userTimestamp = args[0]
			}

			// Timestamp defaults to time.Now() unless it's explicitly set
			var timestamp int64
			if strings.Compare(userTimestamp, "now") == 0 {
				timestamp = tmtime.Now().Unix()
			} else {
				userTimestamp, err := strconv.ParseInt(userTimestamp, 10, 64)
				if err != nil {
					return err
				}
				timestamp = userTimestamp
			}

			// Load hex-encoded cryptographically strong pseudo-random number
			randomNumber, err := types.GenerateSecureRandomNumber()
			if err != nil {
				return err
			}
			randomNumberHash := types.CalculateRandomHash(randomNumber, timestamp)

			// Prepare random number, timestamp, and hash for output
			randomNumberStr := fmt.Sprintf("Random number: %s\n", hex.EncodeToString(randomNumber))
			timestampStr := fmt.Sprintf("Timestamp: %d\n", timestamp)
			randomNumberHashStr := fmt.Sprintf("Random number hash: %s", hex.EncodeToString(randomNumberHash))
			output := []string{randomNumberStr, timestampStr, randomNumberHashStr}
			return clientCtx.PrintObjectLegacy(strings.Join(output, ""))
		},
	}
}

// QueryCalcSwapIDCmd calculates the swapID for a random number hash, sender, and sender other chain
func QueryCalcSwapIDCmd(queryRoute string) *cobra.Command {
	return &cobra.Command{
		Use:     "calc-swapid [random-number-hash] [sender] [sender-other-chain]",
		Short:   "calculate swap ID for the given random number hash, sender, and sender other chain",
		Example: "bep3 calc-swapid 0677bd8a303dd981810f34d8e5cc6507f13b391899b84d3c1be6c6045a17d747 kava1l0xsq2z7gqd7yly0g40y5836g0appumark77ny bnb1ud3q90r98l3mhd87kswv3h8cgrymzeljct8qn7",
		Args:    cobra.MinimumNArgs(3),
		RunE: func(cmd *cobra.Command, args []string) error {
			clientCtx, err := client.GetClientQueryContext(cmd)
			if err != nil {
				return err
			}

			// Parse query params
			randomNumberHash, err := hex.DecodeString(args[0])
			if err != nil {
				return err
			}
			sender, err := sdk.AccAddressFromBech32(args[1])
			if err != nil {
				return err
			}
			senderOtherChain := args[2]

			// Calculate swap ID and convert to human-readable string
			swapID := types.CalculateSwapID(randomNumberHash, sender, senderOtherChain)
			return clientCtx.PrintObjectLegacy(hex.EncodeToString(swapID))
		},
	}
}

// QueryGetAssetSupplyCmd queries as asset's current in swap supply, active, supply, and supply limit
func QueryGetAssetSupplyCmd(queryRoute string) *cobra.Command {
	return &cobra.Command{
		Use:     "supply [denom]",
		Short:   "get information about an asset's supply",
		Example: "bep3 supply bnb",
		Args:    cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			clientCtx, err := client.GetClientQueryContext(cmd)
			if err != nil {
				return err
			}
			queryClient := types.NewQueryClient(clientCtx)

			res, err := queryClient.AssetSupply(context.Background(), &types.QueryAssetSupplyRequest{
				Denom: args[0],
			})
			if err != nil {
				return err
			}

			return clientCtx.PrintProto(res)
		},
	}
}

// QueryGetAssetSuppliesCmd queries AssetSupplies in the store
func QueryGetAssetSuppliesCmd(queryRoute string) *cobra.Command {
	return &cobra.Command{
		Use:     "supplies",
		Short:   "get a list of all asset supplies",
		Example: "bep3 supplies",
		RunE: func(cmd *cobra.Command, args []string) error {
			clientCtx, err := client.GetClientQueryContext(cmd)
			if err != nil {
				return err
			}
			queryClient := types.NewQueryClient(clientCtx)

			res, err := queryClient.AssetSupplies(context.Background(), &types.QueryAssetSuppliesRequest{
				// TODO: Pagination here?
			})
			if err != nil {
				return err
			}

			if len(res.AssetSupplies) == 0 {
				return fmt.Errorf("there are currently no asset supplies")
			}

			return clientCtx.PrintProto(res)
		},
	}
}

// QueryGetAtomicSwapCmd queries an AtomicSwap by swapID
func QueryGetAtomicSwapCmd(queryRoute string) *cobra.Command {
	return &cobra.Command{
		Use:     "swap [swap-id]",
		Short:   "get atomic swap information",
		Example: "bep3 swap 6682c03cc3856879c8fb98c9733c6b0c30758299138166b6523fe94628b1d3af",
		Args:    cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			clientCtx, err := client.GetClientQueryContext(cmd)
			if err != nil {
				return err
			}
			queryClient := types.NewQueryClient(clientCtx)

			res, err := queryClient.AtomicSwap(context.Background(), &types.QueryAtomicSwapRequest{
				SwapId: args[0],
			})
			if err != nil {
				return err
			}

			return clientCtx.PrintProto(res)
		},
	}
}

// QueryGetAtomicSwapsCmd queries AtomicSwaps in the store
func QueryGetAtomicSwapsCmd(queryRoute string) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "swaps",
		Short: "query atomic swaps with optional filters",
		Long: strings.TrimSpace(`Query for all paginated atomic swaps that match optional filters:
Example:
$ kvcli q bep3 swaps --involve=kava1l0xsq2z7gqd7yly0g40y5836g0appumark77ny
$ kvcli q bep3 swaps --expiration=280
$ kvcli q bep3 swaps --status=(Open|Completed|Expired)
$ kvcli q bep3 swaps --direction=(Incoming|Outgoing)
$ kvcli q bep3 swaps --page=2 --limit=100
`,
		),
		RunE: func(cmd *cobra.Command, args []string) error {
			bechInvolveAddr, err := cmd.Flags().GetString(flagInvolve)
			if err != nil {
				return err
			}
			strExpiration, err := cmd.Flags().GetString(flagExpiration)
			if err != nil {
				return err
			}
			strSwapStatus, err := cmd.Flags().GetString(flagStatus)
			if err != nil {
				return err
			}
			strSwapDirection, err := cmd.Flags().GetString(flagDirection)
			if err != nil {
				return err
			}

			pageReq, err := client.ReadPageRequest(cmd.Flags())
			if err != nil {
				return err
			}

			req := types.QueryAtomicSwapsRequest{
				Pagination: pageReq,
			}

			if len(bechInvolveAddr) != 0 {
				involveAddr, err := sdk.AccAddressFromBech32(bechInvolveAddr)
				if err != nil {
					return err
				}
				req.Involve = involveAddr.String()
			}

			if len(strExpiration) != 0 {
				expiration, err := strconv.ParseUint(strExpiration, 10, 64)
				if err != nil {
					return err
				}
				req.Expiration = expiration
			}

			if len(strSwapStatus) != 0 {
				swapStatus := types.NewSwapStatusFromString(strSwapStatus)
				if !swapStatus.IsValid() {
					return fmt.Errorf("invalid swap status %s", strSwapStatus)
				}
				req.Status = swapStatus
			}

			if len(strSwapDirection) != 0 {
				swapDirection := types.NewSwapDirectionFromString(strSwapDirection)
				if !swapDirection.IsValid() {
					return fmt.Errorf("invalid swap direction %s", strSwapDirection)
				}
				req.Direction = swapDirection
			}

			clientCtx, err := client.GetClientQueryContext(cmd)
			if err != nil {
				return err
			}
			queryClient := types.NewQueryClient(clientCtx)

			res, err := queryClient.AtomicSwaps(context.Background(), &req)
			if err != nil {
				return err
			}

			return clientCtx.PrintProto(res)
		},
	}

	cmd.Flags().String(flagInvolve, "", "(optional) filter by atomic swaps that involve an address")
	cmd.Flags().String(flagExpiration, "", "(optional) filter by atomic swaps that expire before a block height")
	cmd.Flags().String(flagStatus, "", "(optional) filter by atomic swap status, status: open/completed/expired")
	cmd.Flags().String(flagDirection, "", "(optional) filter by atomic swap direction, direction: incoming/outgoing")

	flags.AddPaginationFlagsToCmd(cmd, "swaps")

	return cmd
}

// QueryParamsCmd queries the bep3 module parameters
func QueryParamsCmd(queryRoute string) *cobra.Command {
	return &cobra.Command{
		Use:     "params",
		Short:   "get the bep3 module parameters",
		Example: "bep3 params",
		Args:    cobra.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			clientCtx, err := client.GetClientQueryContext(cmd)
			if err != nil {
				return err
			}
			queryClient := types.NewQueryClient(clientCtx)

			res, err := queryClient.Params(context.Background(), &types.QueryParamsRequest{})
			if err != nil {
				return err
			}

			return clientCtx.PrintProto(&res.Params)
		},
	}
}
