package tron

import (
	"context"
	"math/big"
	"strings"

	"github.com/shopspring/decimal"
	"github.com/tech/sendico/gateway/tron/internal/keymanager"
	"github.com/tech/sendico/gateway/tron/internal/service/gateway/tronclient"
	"github.com/tech/sendico/gateway/tron/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"go.uber.org/zap"
)

const (
	// Default fee limit for TRC20 transfers (100 TRX in SUN)
	defaultTRC20FeeLimit = 100_000_000
)

// SubmitTransferNative submits a transfer using native TRON gRPC.
func SubmitTransferNative(
	ctx context.Context,
	logger mlogger.Logger,
	tronClient *tronclient.Client,
	keyManager keymanager.Manager,
	transfer *model.Transfer,
	source *model.ManagedWallet,
	destination string,
) (string, error) {
	if tronClient == nil {
		return "", merrors.Internal("tron driver: tron client not initialized")
	}
	if keyManager == nil {
		return "", merrors.Internal("tron driver: key manager not configured")
	}
	if transfer == nil || source == nil {
		return "", merrors.InvalidArgument("tron driver: transfer context missing")
	}
	if strings.TrimSpace(source.KeyReference) == "" {
		logger.Warn("Source wallet missing key reference", zap.String("wallet_ref", source.WalletRef))
		return "", merrors.InvalidArgument("tron driver: source wallet missing key reference")
	}

	rawFrom := strings.TrimSpace(source.DepositAddress)
	rawTo := strings.TrimSpace(destination)
	if rawFrom == "" || rawTo == "" {
		return "", merrors.InvalidArgument("tron driver: invalid addresses")
	}

	fromAddr, err := normalizeAddress(rawFrom)
	if err != nil {
		logger.Warn("Invalid TRON source address", zap.String("wallet_ref", source.WalletRef), zap.Error(err))
		return "", err
	}
	toAddr, err := normalizeAddress(rawTo)
	if err != nil {
		logger.Warn("Invalid TRON destination address", zap.String("transfer_ref", transfer.TransferRef), zap.Error(err))
		return "", err
	}

	amount := transfer.NetAmount
	if amount == nil || strings.TrimSpace(amount.Amount) == "" {
		logger.Warn("Transfer missing net amount", zap.String("transfer_ref", transfer.TransferRef))
		return "", merrors.InvalidArgument("tron driver: transfer missing net amount")
	}

	contract := strings.TrimSpace(transfer.ContractAddress)
	if contract != "" {
		normalizedContract, err := normalizeAddress(contract)
		if err != nil {
			logger.Warn("Invalid TRON contract address", zap.String("transfer_ref", transfer.TransferRef),
				zap.String("contract_address", contract), zap.Error(err))
			return "", err
		}
		contract = normalizedContract
	}

	logger.Info("Submitting native TRON transfer",
		zap.String("transfer_ref", transfer.TransferRef),
		zap.String("from", fromAddr),
		zap.String("to", toAddr),
		zap.String("contract", contract),
		zap.String("amount", amount.Amount),
	)

	var txID string

	if contract == "" {
		// Native TRX transfer
		txID, err = submitTRXTransfer(ctx, logger, tronClient, keyManager, source, fromAddr, toAddr, amount.Amount)
	} else {
		// TRC20 token transfer
		txID, err = submitTRC20Transfer(ctx, logger, tronClient, keyManager, source, fromAddr, toAddr, contract, amount.Amount)
	}

	if err != nil {
		logger.Warn("Native TRON transfer failed",
			zap.String("transfer_ref", transfer.TransferRef),
			zap.Error(err),
		)
		return "", err
	}

	logger.Info("Native TRON transfer submitted",
		zap.String("transfer_ref", transfer.TransferRef),
		zap.String("tx_id", txID),
	)

	return txID, nil
}

func submitTRXTransfer(
	ctx context.Context,
	logger mlogger.Logger,
	tronClient *tronclient.Client,
	keyManager keymanager.Manager,
	source *model.ManagedWallet,
	from, to, amountStr string,
) (string, error) {
	// Parse amount (should be in base units = SUN for TRX)
	amountSun, err := parseAmountToSun(amountStr)
	if err != nil {
		logger.Warn("Failed to parse TRX amount", zap.String("amount", amountStr), zap.Error(err))
		return "", err
	}

	logger.Debug("Creating TRX transfer transaction",
		zap.String("from", from),
		zap.String("to", to),
		zap.Int64("amount_sun", amountSun),
	)

	// Create unsigned transaction
	txExt, err := tronClient.Transfer(from, to, amountSun)
	if err != nil {
		logger.Warn("Failed to create TRX transfer transaction", zap.Error(err))
		return "", merrors.Internal("tron driver: failed to create transfer: " + err.Error())
	}

	if txExt == nil || txExt.Transaction == nil {
		return "", merrors.Internal("tron driver: nil transaction returned")
	}

	// Sign transaction
	signedTx, err := keyManager.SignTronTransaction(ctx, source.KeyReference, txExt.Transaction)
	if err != nil {
		logger.Warn("Failed to sign TRX transfer", zap.Error(err))
		return "", err
	}

	// Broadcast transaction
	result, err := tronClient.Broadcast(signedTx)
	if err != nil {
		logger.Warn("Failed to broadcast TRX transfer", zap.Error(err))
		return "", merrors.Internal("tron driver: broadcast failed: " + err.Error())
	}

	if result != nil && !result.GetResult() {
		msg := string(result.GetMessage())
		logger.Warn("TRX transfer broadcast rejected", zap.String("message", msg))
		return "", merrors.Internal("tron driver: broadcast rejected: " + msg)
	}

	return tronclient.TxIDFromExtention(txExt), nil
}

func submitTRC20Transfer(
	ctx context.Context,
	logger mlogger.Logger,
	tronClient *tronclient.Client,
	keyManager keymanager.Manager,
	source *model.ManagedWallet,
	from, to, contract, amountStr string,
) (string, error) {
	decimals, err := tronClient.TRC20GetDecimals(contract)
	if err != nil {
		logger.Warn("Failed to read TRC20 decimals", zap.String("contract", contract), zap.Error(err))
		return "", err
	}

	amount, err := toBaseUnits(amountStr, decimals)
	if err != nil {
		logger.Warn("Failed to convert TRC20 amount to base units",
			zap.String("amount", amountStr),
			zap.String("decimals", decimals.String()),
			zap.Error(err),
		)
		return "", err
	}

	logger.Debug("Creating TRC20 transfer transaction",
		zap.String("from", from),
		zap.String("to", to),
		zap.String("contract", contract),
		zap.String("amount_raw", amountStr),
		zap.String("amount_base", amount.String()),
		zap.String("decimals", decimals.String()),
	)

	// Create unsigned transaction
	txExt, err := tronClient.TRC20Send(from, to, contract, amount, defaultTRC20FeeLimit)
	if err != nil {
		logger.Warn("Failed to create TRC20 transfer transaction", zap.Error(err))
		return "", merrors.Internal("tron driver: failed to create TRC20 transfer: " + err.Error())
	}

	if txExt == nil || txExt.Transaction == nil {
		return "", merrors.Internal("tron driver: nil transaction returned")
	}

	// Sign transaction
	signedTx, err := keyManager.SignTronTransaction(ctx, source.KeyReference, txExt.Transaction)
	if err != nil {
		logger.Warn("Failed to sign TRC20 transfer", zap.Error(err))
		return "", err
	}

	// Broadcast transaction
	result, err := tronClient.Broadcast(signedTx)
	if err != nil {
		logger.Warn("Failed to broadcast TRC20 transfer", zap.Error(err))
		return "", merrors.Internal("tron driver: broadcast failed: " + err.Error())
	}

	if result != nil && !result.GetResult() {
		msg := string(result.GetMessage())
		logger.Warn("TRC20 transfer broadcast rejected", zap.String("message", msg))
		return "", merrors.Internal("tron driver: broadcast rejected: " + msg)
	}

	return tronclient.TxIDFromExtention(txExt), nil
}

func parseAmountToSun(amountStr string) (int64, error) {
	trimmed := strings.TrimSpace(amountStr)
	if trimmed == "" {
		return 0, merrors.InvalidArgument("tron driver: amount is required")
	}

	// Amount is already in base units (SUN)
	value, ok := new(big.Int).SetString(trimmed, 10)
	if !ok {
		return 0, merrors.InvalidArgument("tron driver: invalid amount: " + trimmed)
	}

	if value.Sign() < 0 {
		return 0, merrors.InvalidArgument("tron driver: amount must be non-negative")
	}

	if !value.IsInt64() {
		return 0, merrors.InvalidArgument("tron driver: amount exceeds int64 range")
	}

	return value.Int64(), nil
}

func toBaseUnits(amountStr string, decimals *big.Int) (*big.Int, error) {
	trimmed := strings.TrimSpace(amountStr)
	if trimmed == "" {
		return nil, merrors.InvalidArgument("tron driver: amount is required")
	}
	if decimals == nil {
		return nil, merrors.InvalidArgument("tron driver: token decimals missing")
	}
	if decimals.Sign() < 0 {
		return nil, merrors.InvalidArgument("tron driver: token decimals must be non-negative")
	}
	if decimals.BitLen() > 31 {
		return nil, merrors.InvalidArgument("tron driver: token decimals out of range")
	}

	value, err := decimal.NewFromString(trimmed)
	if err != nil {
		return nil, merrors.InvalidArgument("tron driver: invalid amount: " + trimmed)
	}
	if value.IsNegative() {
		return nil, merrors.InvalidArgument("tron driver: amount must be non-negative")
	}

	decimalsValue := decimals.Int64()
	if decimalsValue > 255 {
		return nil, merrors.InvalidArgument("tron driver: token decimals out of range")
	}
	multiplier := decimal.NewFromInt(1)
	for i := int64(0); i < decimalsValue; i++ {
		multiplier = multiplier.Mul(decimal.NewFromInt(10))
	}
	scaled := value.Mul(multiplier)
	if !scaled.Equal(scaled.Truncate(0)) {
		return nil, merrors.InvalidArgument("tron driver: amount " + trimmed + " exceeds token precision")
	}

	return scaled.BigInt(), nil
}

// normalizeTxHash ensures consistent tx hash format (lowercase hex without 0x prefix).
func normalizeTxHash(txHash string) string {
	h := strings.TrimSpace(txHash)
	h = strings.TrimPrefix(h, "0x")
	h = strings.TrimPrefix(h, "0X")
	return strings.ToLower(h)
}
