package config

import (
	"encoding/json"
	"fmt"
	"math"

	"github.com/spf13/viper"
	"google.golang.org/grpc"

	pruningtypes "cosmossdk.io/store/pruning/types"

	"git.cw.tr/mukan-network/mukan-sdk/telemetry"
	sdk "git.cw.tr/mukan-network/mukan-sdk/types"
	sdkerrors "git.cw.tr/mukan-network/mukan-sdk/types/errors"
)

const (
	defaultMinGasPrices = ""

	// DefaultAPIAddress defines the default address to bind the API server to.
	DefaultAPIAddress = "tcp://localhost:1317"

	// DefaultGRPCAddress defines the default address to bind the gRPC server to.
	DefaultGRPCAddress = "localhost:9090"

	// DefaultGRPCMaxRecvMsgSize defines the default gRPC max message size in
	// bytes the server can receive.
	DefaultGRPCMaxRecvMsgSize = 1024 * 1024 * 10

	// DefaultGRPCMaxSendMsgSize defines the default gRPC max message size in
	// bytes the server can send.
	DefaultGRPCMaxSendMsgSize = math.MaxInt32
)

// BaseConfig defines the server's basic configuration
type BaseConfig struct {
	// The minimum gas prices a validator is willing to accept for processing a
	// transaction. A transaction's fees must meet the minimum of any denomination
	// specified in this config (e.g. 0.25token1;0.0001token2).
	MinGasPrices string `mapstructure:"minimum-gas-prices"`

	// The maximum amount of gas a grpc/Rest query may consume.
	// If set to 0, it is unbounded.
	QueryGasLimit uint64 `mapstructure:"query-gas-limit"`

	Pruning           string `mapstructure:"pruning"`
	PruningKeepRecent string `mapstructure:"pruning-keep-recent"`
	PruningInterval   string `mapstructure:"pruning-interval"`

	// HaltHeight contains a non-zero block height at which a node will gracefully
	// halt and shutdown that can be used to assist upgrades and testing.
	//
	// Note: Commitment of state will be attempted on the corresponding block.
	HaltHeight uint64 `mapstructure:"halt-height"`

	// HaltTime contains a non-zero minimum block time (in Unix seconds) at which
	// a node will gracefully halt and shutdown that can be used to assist
	// upgrades and testing.
	//
	// Note: Commitment of state will be attempted on the corresponding block.
	HaltTime uint64 `mapstructure:"halt-time"`

	// MinRetainBlocks defines the minimum block height offset from the current
	// block being committed, such that blocks past this offset may be pruned
	// from CometBFT. It is used as part of the process of determining the
	// ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates
	// that no blocks should be pruned.
	//
	// This configuration value is only responsible for pruning CometBFT blocks.
	// It has no bearing on application state pruning which is determined by the
	// "pruning-*" configurations.
	//
	// Note: CometBFT block pruning is dependant on this parameter in conjunction
	// with the unbonding (safety threshold) period, state pruning and state sync
	// snapshot parameters to determine the correct minimum value of
	// ResponseCommit.RetainHeight.
	MinRetainBlocks uint64 `mapstructure:"min-retain-blocks"`

	// InterBlockCache enables inter-block caching.
	InterBlockCache bool `mapstructure:"inter-block-cache"`

	// IndexEvents defines the set of events in the form {eventType}.{attributeKey},
	// which informs CometBFT what to index. If empty, all events will be indexed.
	IndexEvents []string `mapstructure:"index-events"`

	// IavlCacheSize set the size of the iavl tree cache.
	IAVLCacheSize uint64 `mapstructure:"iavl-cache-size"`

	// IAVLDisableFastNode enables or disables the fast sync node.
	IAVLDisableFastNode bool `mapstructure:"iavl-disable-fastnode"`

	// AppDBBackend defines the type of Database to use for the application and snapshots databases.
	// An empty string indicates that the CometBFT config's DBBackend value should be used.
	AppDBBackend string `mapstructure:"app-db-backend"`
}

// APIConfig defines the API listener configuration.
type APIConfig struct {
	// Enable defines if the API server should be enabled.
	Enable bool `mapstructure:"enable"`

	// Swagger defines if swagger documentation should automatically be registered.
	Swagger bool `mapstructure:"swagger"`

	// EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk)
	EnableUnsafeCORS bool `mapstructure:"enabled-unsafe-cors"`

	// Address defines the API server to listen on
	Address string `mapstructure:"address"`

	// MaxOpenConnections defines the number of maximum open connections
	MaxOpenConnections uint `mapstructure:"max-open-connections"`

	// RPCReadTimeout defines the CometBFT RPC read timeout (in seconds)
	RPCReadTimeout uint `mapstructure:"rpc-read-timeout"`

	// RPCWriteTimeout defines the CometBFT RPC write timeout (in seconds)
	RPCWriteTimeout uint `mapstructure:"rpc-write-timeout"`

	// RPCMaxBodyBytes defines the CometBFT maximum request body (in bytes)
	RPCMaxBodyBytes uint `mapstructure:"rpc-max-body-bytes"`

	// TODO: TLS/Proxy configuration.
	//
	// Ref: https://git.cw.tr/mukan-network/mukan-sdk/issues/6420
}

// BlockRange represents a range of block heights as [start_block, end_block] (inclusive).
// It is used to map gRPC historical connections to specific block height ranges for routing
// historical queries to appropriate archive nodes.
//
// Example:
//   - [0, 1000] represents blocks from genesis (0) through block 1000
//   - [1001, 2000] represents blocks from 1001 through 2000
//
// Both start and end blocks must be non-negative, and start must be less than or equal to end.
type BlockRange [2]int

// HistoricalGRPCConnections is a map of block ranges to gRPC client connections
// used for routing requests to different backend nodes based on block height.
type HistoricalGRPCConnections map[BlockRange]*grpc.ClientConn

// GRPCConfig defines configuration for the gRPC server.
type GRPCConfig struct {
	// Enable defines if the gRPC server should be enabled.
	Enable bool `mapstructure:"enable"`

	// Address defines the API server to listen on
	Address string `mapstructure:"address"`

	// MaxRecvMsgSize defines the max message size in bytes the server can receive.
	// The default value is 10MB.
	MaxRecvMsgSize int `mapstructure:"max-recv-msg-size"`

	// MaxSendMsgSize defines the max message size in bytes the server can send.
	// The default value is math.MaxInt32.
	MaxSendMsgSize int `mapstructure:"max-send-msg-size"`

	// SkipCheckHeader defines if the gRPC server should bypass check header.
	SkipCheckHeader bool `mapstructure:"skip-check-header"`

	// HistoricalGRPCAddressBlockRange maps block ranges to gRPC addresses for routing historical queries.
	HistoricalGRPCAddressBlockRange map[BlockRange]string `mapstructure:"-"`
}

// GRPCWebConfig defines configuration for the gRPC-web server.
type GRPCWebConfig struct {
	// Enable defines if the gRPC-web should be enabled.
	Enable bool `mapstructure:"enable"`
}

// StateSyncConfig defines the state sync snapshot configuration.
type StateSyncConfig struct {
	// SnapshotInterval sets the interval at which state sync snapshots are taken.
	// 0 disables snapshots.
	SnapshotInterval uint64 `mapstructure:"snapshot-interval"`

	// SnapshotKeepRecent sets the number of recent state sync snapshots to keep.
	// 0 keeps all snapshots.
	SnapshotKeepRecent uint32 `mapstructure:"snapshot-keep-recent"`
}

// MempoolConfig defines the configurations for the SDK built-in app-side mempool
// implementations.
type MempoolConfig struct {
	// MaxTxs defines the behavior of the mempool. A negative value indicates
	// the mempool is disabled entirely, zero indicates that the mempool is
	// unbounded in how many txs it may contain, and a positive value indicates
	// the maximum amount of txs it may contain.
	MaxTxs int `mapstructure:"max-txs"`
}

// State Streaming configuration
type (
	// StreamingConfig defines application configuration for external streaming services
	StreamingConfig struct {
		ABCI ABCIListenerConfig `mapstructure:"abci"`
	}
	// ABCIListenerConfig defines application configuration for ABCIListener streaming service
	ABCIListenerConfig struct {
		Keys          []string `mapstructure:"keys"`
		Plugin        string   `mapstructure:"plugin"`
		StopNodeOnErr bool     `mapstructure:"stop-node-on-err"`
	}
)

// Config defines the server's top level configuration
type Config struct {
	BaseConfig `mapstructure:",squash"`

	// Telemetry defines the application telemetry configuration
	Telemetry telemetry.Config `mapstructure:"telemetry"`
	API       APIConfig        `mapstructure:"api"`
	GRPC      GRPCConfig       `mapstructure:"grpc"`
	GRPCWeb   GRPCWebConfig    `mapstructure:"grpc-web"`
	StateSync StateSyncConfig  `mapstructure:"state-sync"`
	Streaming StreamingConfig  `mapstructure:"streaming"`
	Mempool   MempoolConfig    `mapstructure:"mempool"`
}

// SetMinGasPrices sets the validator's minimum gas prices.
func (c *Config) SetMinGasPrices(gasPrices sdk.DecCoins) {
	c.MinGasPrices = gasPrices.String()
}

// GetMinGasPrices returns the validator's minimum gas prices based on the set configuration.
func (c *Config) GetMinGasPrices() sdk.DecCoins {
	if c.MinGasPrices == "" {
		return sdk.DecCoins{}
	}

	gasPrices, err := sdk.ParseDecCoins(c.MinGasPrices)
	if err != nil {
		panic(fmt.Sprintf("invalid minimum gas prices: %v", err))
	}

	return gasPrices
}

// DefaultConfig returns server's default configuration.
func DefaultConfig() *Config {
	return &Config{
		BaseConfig: BaseConfig{
			MinGasPrices:        defaultMinGasPrices,
			QueryGasLimit:       0,
			InterBlockCache:     true,
			Pruning:             pruningtypes.PruningOptionDefault,
			PruningKeepRecent:   "0",
			PruningInterval:     "0",
			MinRetainBlocks:     0,
			IndexEvents:         make([]string, 0),
			IAVLCacheSize:       781250,
			IAVLDisableFastNode: false,
			AppDBBackend:        "",
		},
		Telemetry: telemetry.Config{
			Enabled:      false,
			GlobalLabels: [][]string{},
		},
		API: APIConfig{
			Enable:             false,
			Swagger:            false,
			Address:            DefaultAPIAddress,
			MaxOpenConnections: 1000,
			RPCReadTimeout:     10,
			RPCMaxBodyBytes:    1000000,
		},
		GRPC: GRPCConfig{
			Enable:          true,
			Address:         DefaultGRPCAddress,
			MaxRecvMsgSize:  DefaultGRPCMaxRecvMsgSize,
			MaxSendMsgSize:  DefaultGRPCMaxSendMsgSize,
			SkipCheckHeader: false,
		},
		GRPCWeb: GRPCWebConfig{
			Enable: true,
		},
		StateSync: StateSyncConfig{
			SnapshotInterval:   0,
			SnapshotKeepRecent: 2,
		},
		Streaming: StreamingConfig{
			ABCI: ABCIListenerConfig{
				Keys:          []string{},
				StopNodeOnErr: true,
			},
		},
		Mempool: MempoolConfig{
			MaxTxs: -1,
		},
	}
}

// GetConfig returns a fully parsed Config object.
func GetConfig(v *viper.Viper) (Config, error) {
	conf := DefaultConfig()
	if err := v.Unmarshal(conf); err != nil {
		return Config{}, fmt.Errorf("error extracting app config: %w", err)
	}
	raw := v.GetString("grpc.historical-grpc-address-block-range")
	if len(raw) > 0 {
		data := make(map[string]BlockRange)
		if err := json.Unmarshal([]byte(raw), &data); err != nil {
			return Config{}, fmt.Errorf("failed to parse historical-grpc-address-block-range as JSON: %w (value: %s)", err, raw)
		}
		historicalGRPCAddressBlockRange := make(map[BlockRange]string, len(data))
		for address, blockRange := range data {
			if blockRange[0] < 0 || blockRange[1] < 0 {
				return Config{}, fmt.Errorf("invalid block range [%d, %d] for address %s: block numbers cannot be negative",
					blockRange[0], blockRange[1], address)
			}
			if blockRange[0] > blockRange[1] {
				return Config{}, fmt.Errorf("invalid block range [%d, %d] for address %s: start block must be <= end block",
					blockRange[0], blockRange[1], address)
			}
			historicalGRPCAddressBlockRange[blockRange] = address
		}
		conf.GRPC.HistoricalGRPCAddressBlockRange = historicalGRPCAddressBlockRange
	}
	return *conf, nil
}

// ValidateBasic returns an error if min-gas-prices field is empty in BaseConfig. Otherwise, it returns nil.
func (c Config) ValidateBasic() error {
	if c.MinGasPrices == "" {
		return sdkerrors.ErrAppConfig.Wrap("set min gas price in app.toml or flag or env variable")
	}
	if c.Pruning == pruningtypes.PruningOptionEverything && c.StateSync.SnapshotInterval > 0 {
		return sdkerrors.ErrAppConfig.Wrapf(
			"cannot enable state sync snapshots with '%s' pruning setting", pruningtypes.PruningOptionEverything,
		)
	}

	return nil
}
