package managedkey

import (
	"context"
	"encoding/hex"
	"math/big"
	"path"
	"strings"

	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/vault/kv"
	"go.uber.org/zap"
)

const defaultComponent = "vault key manager"

type service struct {
	logger    mlogger.Logger
	component string
	store     kv.Client
	keyPrefix string
}

func newService(opts Options) (Service, error) {
	logger := opts.Logger
	if logger == nil {
		logger = zap.NewNop()
	}

	component := strings.TrimSpace(opts.Component)
	if component == "" {
		component = defaultComponent
	}

	store, err := kv.New(kv.Options{
		Logger: logger,
		Config: kv.Config{
			Address:      opts.Config.Address,
			TokenEnv:     opts.Config.TokenEnv,
			TokenFileEnv: opts.Config.TokenFileEnv,
			TokenFile:    opts.Config.TokenFile,
			Namespace:    opts.Config.Namespace,
			MountPath:    opts.Config.MountPath,
		},
		Component: component,
	})
	if err != nil {
		return nil, err
	}

	keyPrefix := strings.Trim(strings.TrimSpace(opts.Config.KeyPrefix), "/")
	if keyPrefix == "" {
		keyPrefix = strings.Trim(strings.TrimSpace(opts.DefaultKeyPrefix), "/")
	}
	if keyPrefix == "" {
		keyPrefix = "wallets"
	}

	return &service{
		logger:    logger.Named("vault"),
		component: component,
		store:     store,
		keyPrefix: keyPrefix,
	}, nil
}

func (s *service) CreateManagedWalletKey(ctx context.Context, walletRef, network string) (*ManagedWalletKey, error) {
	walletRef = strings.TrimSpace(walletRef)
	network = strings.TrimSpace(network)
	if walletRef == "" {
		s.logger.Warn("WalletRef missing for managed key creation", zap.String("network", network))
		return nil, merrors.InvalidArgument(s.component+": walletRef is required", "wallet_ref")
	}
	if network == "" {
		s.logger.Warn("Network missing for managed key creation", zap.String("wallet_ref", walletRef))
		return nil, merrors.InvalidArgument(s.component+": network is required", "network")
	}

	privateKey, err := crypto.GenerateKey()
	if err != nil {
		s.logger.Warn("Failed to generate managed wallet key", zap.String("wallet_ref", walletRef), zap.String("network", network), zap.Error(err))
		return nil, merrors.Internal(s.component + ": failed to generate key: " + err.Error())
	}

	privateKeyBytes := crypto.FromECDSA(privateKey)
	publicKey := privateKey.PublicKey
	publicKeyBytes := crypto.FromECDSAPub(&publicKey)
	publicKeyHex := hex.EncodeToString(publicKeyBytes)
	address := strings.ToLower(crypto.PubkeyToAddress(publicKey).Hex())
	keyID := s.BuildKeyID(network, walletRef)

	payload := map[string]interface{}{
		"private_key": hex.EncodeToString(privateKeyBytes),
		"public_key":  hex.EncodeToString(publicKeyBytes),
		"address":     address,
		"network":     strings.ToLower(network),
	}
	if err := s.store.Put(ctx, keyID, payload); err != nil {
		s.logger.Warn("Failed to persist managed wallet key", zap.String("wallet_ref", walletRef), zap.String("network", network), zap.Error(err))
		zeroBytes(privateKeyBytes)
		zeroBytes(publicKeyBytes)
		return nil, err
	}

	zeroBytes(privateKeyBytes)
	zeroBytes(publicKeyBytes)

	s.logger.Info("Managed wallet key created", zap.String("wallet_ref", walletRef), zap.String("network", network), zap.String("address", address))

	return &ManagedWalletKey{
		KeyID:     keyID,
		Address:   address,
		PublicKey: publicKeyHex,
	}, nil
}

func (s *service) SignEVMTransaction(ctx context.Context, keyID string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
	if strings.TrimSpace(keyID) == "" {
		s.logger.Warn("Signing failed: empty key id")
		return nil, merrors.InvalidArgument(s.component+": keyID is required", "key_id")
	}
	if tx == nil {
		s.logger.Warn("Signing failed: nil transaction", zap.String("key_id", keyID))
		return nil, merrors.InvalidArgument(s.component+": transaction is nil", "transaction")
	}
	if chainID == nil {
		s.logger.Warn("Signing failed: nil chain id", zap.String("key_id", keyID))
		return nil, merrors.InvalidArgument(s.component+": chainID is nil", "chain_id")
	}

	material, err := s.LoadKeyMaterial(ctx, keyID)
	if err != nil {
		s.logger.Warn("Failed to load key material", zap.String("key_id", keyID), zap.Error(err))
		return nil, err
	}

	keyBytes, err := hex.DecodeString(material.PrivateKey)
	if err != nil {
		s.logger.Warn("Invalid key material", zap.String("key_id", keyID), zap.Error(err))
		return nil, merrors.Internal(s.component + ": invalid key material: " + err.Error())
	}
	defer zeroBytes(keyBytes)

	privateKey, err := crypto.ToECDSA(keyBytes)
	if err != nil {
		s.logger.Warn("Failed to construct private key", zap.String("key_id", keyID), zap.Error(err))
		return nil, merrors.Internal(s.component + ": failed to construct private key: " + err.Error())
	}

	signed, err := types.SignTx(tx, types.LatestSignerForChainID(chainID), privateKey)
	if err != nil {
		s.logger.Warn("Failed to sign transaction", zap.String("key_id", keyID), zap.Error(err))
		return nil, merrors.Internal(s.component + ": failed to sign transaction: " + err.Error())
	}

	s.logger.Info("Transaction signed with managed key", zap.String("key_id", keyID), zap.String("network", material.Network), zap.String("tx_hash", signed.Hash().Hex()))

	return signed, nil
}

func (s *service) LoadKeyMaterial(ctx context.Context, keyID string) (*Material, error) {
	data, err := s.store.Get(ctx, keyID)
	if err != nil {
		return nil, err
	}

	secretPath := strings.Trim(strings.TrimPrefix(strings.TrimSpace(keyID), "/"), "/")
	privateKey, err := fieldAsString(data, "private_key", secretPath, s.component)
	if err != nil {
		return nil, err
	}
	publicKey, err := fieldAsString(data, "public_key", secretPath, s.component)
	if err != nil {
		return nil, err
	}
	address, err := fieldAsString(data, "address", secretPath, s.component)
	if err != nil {
		return nil, err
	}
	network, err := fieldAsString(data, "network", secretPath, s.component)
	if err != nil {
		return nil, err
	}

	return &Material{
		PrivateKey: privateKey,
		PublicKey:  publicKey,
		Address:    address,
		Network:    network,
	}, nil
}

func (s *service) BuildKeyID(network, walletRef string) string {
	net := strings.Trim(strings.ToLower(strings.TrimSpace(network)), "/")
	ref := strings.Trim(strings.TrimSpace(walletRef), "/")
	return path.Join(s.keyPrefix, net, ref)
}

func fieldAsString(data map[string]interface{}, field, secretPath, component string) (string, error) {
	value, ok := data[field]
	if !ok {
		return "", merrors.Internal(component + ": secret " + secretPath + " missing " + field)
	}
	str, ok := value.(string)
	if !ok || strings.TrimSpace(str) == "" {
		return "", merrors.Internal(component + ": secret " + secretPath + " invalid " + field)
	}
	return str, nil
}

func zeroBytes(data []byte) {
	for i := range data {
		data[i] = 0
	}
}

var _ Service = (*service)(nil)
