package shared

import (
	"strings"

	"github.com/tech/sendico/gateway/chain/storage/model"
	pmodel "github.com/tech/sendico/pkg/model"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	"go.mongodb.org/mongo-driver/v2/bson"
)

// CloneMoney defensively copies a Money proto.
func CloneMoney(m *moneyv1.Money) *moneyv1.Money {
	if m == nil {
		return nil
	}
	return &moneyv1.Money{Amount: m.GetAmount(), Currency: m.GetCurrency()}
}

// CloneMetadata defensively copies metadata maps.
func CloneMetadata(input map[string]string) map[string]string {
	if len(input) == 0 {
		return nil
	}
	clone := make(map[string]string, len(input))
	for k, v := range input {
		clone[k] = v
	}
	return clone
}

// ResolveContractAddress finds a token contract for a symbol in a case-insensitive manner.
func ResolveContractAddress(tokens []TokenContract, symbol string) string {
	upper := strings.ToUpper(symbol)
	for _, token := range tokens {
		if strings.EqualFold(token.Symbol, upper) && token.ContractAddress != "" {
			return strings.ToLower(token.ContractAddress)
		}
	}
	return ""
}

func GenerateWalletRef() string {
	return bson.NewObjectID().Hex()
}

func GenerateTransferRef() string {
	return bson.NewObjectID().Hex()
}

func ChainKeyFromEnum(chain chainv1.ChainNetwork) pmodel.ChainNetwork {
	switch chain {
	case chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE:
		return pmodel.ChainNetworkArbitrumOne
	case chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_SEPOLIA:
		return pmodel.ChainNetworkArbitrumSepolia
	case chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET:
		return pmodel.ChainNetworkEthereumMainnet
	case chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET:
		return pmodel.ChainNetworkTronMainnet
	case chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE:
		return pmodel.ChainNetworkTronNile
	default:
		return pmodel.ChainNetworkUnspecified
	}
}

func ChainEnumFromName(name pmodel.ChainNetwork) chainv1.ChainNetwork {
	switch name {
	case pmodel.ChainNetworkArbitrumOne:
		return chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE
	case pmodel.ChainNetworkArbitrumSepolia:
		return chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_SEPOLIA
	case pmodel.ChainNetworkEthereumMainnet:
		return chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET
	case pmodel.ChainNetworkTronMainnet:
		return chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET
	case pmodel.ChainNetworkTronNile:
		return chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE
	default:
		return chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED
	}
}

func ManagedWalletStatusToProto(status model.ManagedWalletStatus) chainv1.ManagedWalletStatus {
	switch status {
	case model.ManagedWalletStatusActive:
		return chainv1.ManagedWalletStatus_MANAGED_WALLET_ACTIVE
	case model.ManagedWalletStatusSuspended:
		return chainv1.ManagedWalletStatus_MANAGED_WALLET_SUSPENDED
	case model.ManagedWalletStatusClosed:
		return chainv1.ManagedWalletStatus_MANAGED_WALLET_CLOSED
	default:
		return chainv1.ManagedWalletStatus_MANAGED_WALLET_STATUS_UNSPECIFIED
	}
}

func TransferStatusToModel(status chainv1.TransferStatus) model.TransferStatus {
	switch status {

	case chainv1.TransferStatus_TRANSFER_CREATED:
		return model.TransferStatusCreated

	case chainv1.TransferStatus_TRANSFER_PROCESSING:
		return model.TransferStatusProcessing

	case chainv1.TransferStatus_TRANSFER_WAITING:
		return model.TransferStatusWaiting

	case chainv1.TransferStatus_TRANSFER_SUCCESS:
		return model.TransferStatusSuccess

	case chainv1.TransferStatus_TRANSFER_FAILED:
		return model.TransferStatusFailed

	case chainv1.TransferStatus_TRANSFER_CANCELLED:
		return model.TransferStatusCancelled

	default:
		return model.TransferStatus("")
	}
}

func TransferStatusToProto(status model.TransferStatus) chainv1.TransferStatus {
	switch status {

	case model.TransferStatusCreated:
		return chainv1.TransferStatus_TRANSFER_CREATED

	case model.TransferStatusProcessing:
		return chainv1.TransferStatus_TRANSFER_PROCESSING

	case model.TransferStatusWaiting:
		return chainv1.TransferStatus_TRANSFER_WAITING

	case model.TransferStatusSuccess:
		return chainv1.TransferStatus_TRANSFER_SUCCESS

	case model.TransferStatusFailed:
		return chainv1.TransferStatus_TRANSFER_FAILED

	case model.TransferStatusCancelled:
		return chainv1.TransferStatus_TRANSFER_CANCELLED

	default:
		return chainv1.TransferStatus_TRANSFER_STATUS_UNSPECIFIED
	}
}

func ChainTransferStatusToOperation(status chainv1.TransferStatus) connectorv1.OperationStatus {
	switch status {

	case chainv1.TransferStatus_TRANSFER_CREATED:
		return connectorv1.OperationStatus_OPERATION_CREATED

	case chainv1.TransferStatus_TRANSFER_PROCESSING:
		return connectorv1.OperationStatus_OPERATION_PROCESSING

	case chainv1.TransferStatus_TRANSFER_WAITING:
		return connectorv1.OperationStatus_OPERATION_WAITING

	case chainv1.TransferStatus_TRANSFER_SUCCESS:
		return connectorv1.OperationStatus_OPERATION_SUCCESS

	case chainv1.TransferStatus_TRANSFER_FAILED:
		return connectorv1.OperationStatus_OPERATION_FAILED

	case chainv1.TransferStatus_TRANSFER_CANCELLED:
		return connectorv1.OperationStatus_OPERATION_CANCELLED

	default:
		return connectorv1.OperationStatus_OPERATION_STATUS_UNSPECIFIED
	}
}

// NativeCurrency returns the canonical native token symbol for a network.
func NativeCurrency(network Network) string {
	currency := strings.ToUpper(strings.TrimSpace(network.NativeToken))
	if currency == "" {
		currency = strings.ToUpper(network.Name.String())
	}
	return currency
}

// Network describes a supported blockchain network and known token contracts.
type Network struct {
	Name           pmodel.ChainNetwork
	RPCURL         string
	ChainID        uint64
	NativeToken    string
	TokenConfigs   []TokenContract
	GasTopUpPolicy *GasTopUpPolicy
}

// TokenContract captures the metadata needed to work with a specific on-chain token.
type TokenContract struct {
	Symbol          string
	ContractAddress string
}

// ServiceWallet captures the managed service wallet configuration.
type ServiceWallet struct {
	Network    pmodel.ChainNetwork
	Address    string
	PrivateKey string
}

func ProtoToMoney(money *moneyv1.Money) *paymenttypes.Money {
	if money == nil {
		return &paymenttypes.Money{}
	}
	return &paymenttypes.Money{
		Amount:   money.GetAmount(),
		Currency: money.GetCurrency(),
	}
}

func MonenyToProto(money *paymenttypes.Money) *moneyv1.Money {
	if money == nil {
		return &moneyv1.Money{}
	}
	return &moneyv1.Money{
		Amount:   money.Amount,
		Currency: money.Currency,
	}
}
