package tron

import (
	"context"
	"strings"

	"github.com/ethereum/go-ethereum/core/types"
	"github.com/tech/sendico/gateway/tron/internal/service/gateway/driver"
	"github.com/tech/sendico/gateway/tron/internal/service/gateway/driver/evm"
	"github.com/tech/sendico/gateway/tron/shared"
	"github.com/tech/sendico/gateway/tron/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	"go.uber.org/zap"
)

// Driver implements Tron-specific behavior, including address conversion.
type Driver struct {
	logger mlogger.Logger
}

func New(logger mlogger.Logger) *Driver {
	return &Driver{logger: logger.Named("tron")}
}

func (d *Driver) Name() string {
	return "tron"
}

func (d *Driver) FormatAddress(address string) (string, error) {
	d.logger.Debug("Format address", zap.String("address", address))
	normalized, err := normalizeAddress(address)
	if err != nil {
		d.logger.Warn("Format address failed", zap.String("address", address), zap.Error(err))
	}
	return normalized, err
}

func (d *Driver) NormalizeAddress(address string) (string, error) {
	d.logger.Debug("Normalize address", zap.String("address", address))
	normalized, err := normalizeAddress(address)
	if err != nil {
		d.logger.Warn("Normalize address failed", zap.String("address", address), zap.Error(err))
	}
	return normalized, err
}

func (d *Driver) Balance(ctx context.Context, deps driver.Deps, network shared.Network, wallet *model.ManagedWallet) (*moneyv1.Money, error) {
	if wallet == nil {
		return nil, merrors.InvalidArgument("wallet is required")
	}
	d.logger.Debug("Balance request", zap.String("wallet_ref", wallet.WalletRef), zap.String("network", network.Name.String()))
	rpcAddr, err := rpcAddress(wallet.DepositAddress)
	if err != nil {
		d.logger.Warn("Balance address conversion failed", zap.Error(err),
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("address", wallet.DepositAddress),
		)
		return nil, err
	}
	driverDeps := deps
	driverDeps.Logger = d.logger
	result, err := evm.Balance(ctx, driverDeps, network, wallet, rpcAddr)
	if err != nil {
		d.logger.Warn("Balance failed", zap.Error(err),
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("network", network.Name.String()),
		)
	} else if result != nil {
		d.logger.Debug("Balance result",
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("network", network.Name.String()),
			zap.String("amount", result.Amount),
			zap.String("currency", result.Currency),
		)
	}
	return result, err
}

func (d *Driver) NativeBalance(ctx context.Context, deps driver.Deps, network shared.Network, wallet *model.ManagedWallet) (*moneyv1.Money, error) {
	if wallet == nil {
		return nil, merrors.InvalidArgument("wallet is required")
	}
	d.logger.Debug("Native balance request", zap.String("wallet_ref", wallet.WalletRef), zap.String("network", network.Name.String()))
	rpcAddr, err := rpcAddress(wallet.DepositAddress)
	if err != nil {
		d.logger.Warn("Native balance address conversion failed", zap.Error(err),
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("address", wallet.DepositAddress),
		)
		return nil, err
	}
	driverDeps := deps
	driverDeps.Logger = d.logger
	result, err := evm.NativeBalance(ctx, driverDeps, network, wallet, rpcAddr)
	if err != nil {
		d.logger.Warn("Native balance failed", zap.Error(err),
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("network", network.Name.String()),
		)
	} else if result != nil {
		d.logger.Debug("Native balance result",
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("network", network.Name.String()),
			zap.String("amount", result.Amount),
			zap.String("currency", result.Currency),
		)
	}
	return result, err
}

func (d *Driver) EstimateFee(ctx context.Context, deps driver.Deps, network shared.Network, wallet *model.ManagedWallet, destination string, amount *moneyv1.Money) (*moneyv1.Money, error) {
	if wallet == nil {
		return nil, merrors.InvalidArgument("wallet is required")
	}
	if amount == nil {
		return nil, merrors.InvalidArgument("amount is required")
	}
	d.logger.Debug("Estimate fee request",
		zap.String("wallet_ref", wallet.WalletRef),
		zap.String("network", network.Name.String()),
		zap.String("destination", destination),
	)
	rpcFrom, err := rpcAddress(wallet.DepositAddress)
	if err != nil {
		d.logger.Warn("Estimate fee address conversion failed", zap.Error(err),
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("address", wallet.DepositAddress),
		)
		return nil, err
	}
	rpcTo, err := rpcAddress(destination)
	if err != nil {
		d.logger.Warn("Estimate fee destination conversion failed", zap.Error(err),
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("destination", destination),
		)
		return nil, err
	}
	if rpcFrom == rpcTo {
		return &moneyv1.Money{
			Currency: nativeCurrency(network),
			Amount:   "0",
		}, nil
	}
	driverDeps := deps
	driverDeps.Logger = d.logger
	result, err := evm.EstimateFee(ctx, driverDeps, network, wallet, rpcFrom, rpcTo, amount)
	if err != nil {
		d.logger.Warn("Estimate fee failed", zap.Error(err),
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("network", network.Name.String()),
			zap.String("from_address", wallet.DepositAddress),
			zap.String("from_rpc", rpcFrom),
			zap.String("to_address", destination),
			zap.String("to_rpc", rpcTo),
		)
	} else if result != nil {
		d.logger.Debug("Estimate fee result",
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("network", network.Name.String()),
			zap.String("amount", result.Amount),
			zap.String("currency", result.Currency),
		)
	}
	return result, err
}

func (d *Driver) SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Network, transfer *model.Transfer, source *model.ManagedWallet, destination string) (string, error) {
	if source == nil {
		return "", merrors.InvalidArgument("source wallet is required")
	}
	d.logger.Debug("Submit transfer request",
		zap.String("transfer_ref", transfer.TransferRef),
		zap.String("network", network.Name.String()),
		zap.String("destination", destination),
	)

	// Check if native TRON gRPC is available
	if deps.TronRegistry != nil && deps.TronRegistry.HasClient(network.Name.String()) {
		d.logger.Debug("Using native TRON gRPC for transfer",
			zap.String("transfer_ref", transfer.TransferRef),
			zap.String("network", network.Name.String()),
		)
		tronClient, err := deps.TronRegistry.Client(network.Name.String())
		if err != nil {
			d.logger.Warn("Failed to get TRON client, falling back to EVM", zap.Error(err))
		} else {
			txHash, err := SubmitTransferNative(ctx, d.logger, tronClient, deps.KeyManager, transfer, source, destination)
			if err != nil {
				d.logger.Warn("Native TRON transfer failed", zap.Error(err),
					zap.String("transfer_ref", transfer.TransferRef),
					zap.String("network", network.Name.String()),
				)
			} else {
				d.logger.Debug("Native TRON transfer result",
					zap.String("transfer_ref", transfer.TransferRef),
					zap.String("network", network.Name.String()),
					zap.String("tx_hash", txHash),
				)
			}
			return txHash, err
		}
	}

	// Fallback to EVM JSON-RPC
	rpcFrom, err := rpcAddress(source.DepositAddress)
	if err != nil {
		d.logger.Warn("Submit transfer address conversion failed", zap.Error(err),
			zap.String("wallet_ref", source.WalletRef),
			zap.String("address", source.DepositAddress),
		)
		return "", err
	}
	rpcTo, err := rpcAddress(destination)
	if err != nil {
		d.logger.Warn("Submit transfer destination conversion failed", zap.Error(err),
			zap.String("transfer_ref", transfer.TransferRef),
			zap.String("destination", destination),
		)
		return "", err
	}
	driverDeps := deps
	driverDeps.Logger = d.logger
	txHash, err := evm.SubmitTransfer(ctx, driverDeps, network, transfer, source, rpcFrom, rpcTo)
	if err != nil {
		d.logger.Warn("Submit transfer failed", zap.Error(err),
			zap.String("transfer_ref", transfer.TransferRef),
			zap.String("network", network.Name.String()),
		)
	} else {
		d.logger.Debug("Submit transfer result",
			zap.String("transfer_ref", transfer.TransferRef),
			zap.String("network", network.Name.String()),
			zap.String("tx_hash", txHash),
		)
	}
	return txHash, err
}

func (d *Driver) AwaitConfirmation(ctx context.Context, deps driver.Deps, network shared.Network, txHash string) (*types.Receipt, error) {
	d.logger.Debug("Awaiting confirmation",
		zap.String("tx_hash", txHash),
		zap.String("network", network.Name.String()),
	)

	// Check if native TRON gRPC is available
	if deps.TronRegistry != nil && deps.TronRegistry.HasClient(network.Name.String()) {
		d.logger.Debug("Using native TRON gRPC for confirmation",
			zap.String("tx_hash", txHash),
			zap.String("network", network.Name.String()),
		)
		tronClient, err := deps.TronRegistry.Client(network.Name.String())
		if err != nil {
			d.logger.Warn("Failed to get TRON client, falling back to EVM", zap.Error(err))
		} else {
			receipt, err := AwaitConfirmationNative(ctx, d.logger, tronClient, txHash)
			if err != nil {
				d.logger.Warn("Native TRON confirmation failed", zap.Error(err),
					zap.String("tx_hash", txHash),
					zap.String("network", network.Name.String()),
				)
			} else if receipt != nil {
				d.logger.Debug("Native TRON confirmation result",
					zap.String("tx_hash", txHash),
					zap.String("network", network.Name.String()),
					zap.Uint64("block_number", receipt.BlockNumber.Uint64()),
					zap.Uint64("status", receipt.Status),
				)
			}
			return receipt, err
		}
	}

	// Fallback to EVM JSON-RPC
	driverDeps := deps
	driverDeps.Logger = d.logger
	receipt, err := evm.AwaitConfirmation(ctx, driverDeps, network, txHash)
	if err != nil {
		d.logger.Warn("Awaiting of confirmation failed", zap.Error(err),
			zap.String("tx_hash", txHash),
			zap.String("network", network.Name.String()),
		)
	} else if receipt != nil {
		d.logger.Debug("Await confirmation result",
			zap.String("tx_hash", txHash),
			zap.String("network", network.Name.String()),
			zap.Uint64("block_number", receipt.BlockNumber.Uint64()),
			zap.Uint64("status", receipt.Status),
		)
	}
	return receipt, err
}

func nativeCurrency(network shared.Network) string {
	currency := strings.ToUpper(strings.TrimSpace(network.NativeToken))
	if currency == "" {
		currency = strings.ToUpper(network.Name.String())
	}
	return currency
}

var _ driver.Driver = (*Driver)(nil)
