package ethereum

import (
	"context"

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

// Driver implements Ethereum-specific behavior using the shared EVM logic.
type Driver struct {
	logger mlogger.Logger
}

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

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

func (d *Driver) FormatAddress(address string) (string, error) {
	d.logger.Debug("Format address", zap.String("address", address))
	normalized, err := evm.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 := evm.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) {
	d.logger.Debug("Balance request",
		zap.String("wallet_ref", wallet.WalletRef),
		zap.String("network", string(network.Name)),
	)
	driverDeps := deps
	driverDeps.Logger = d.logger
	result, err := evm.Balance(ctx, driverDeps, network, wallet, wallet.DepositAddress)
	if err != nil {
		d.logger.Warn("Balance failed",
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("network", string(network.Name)),
			zap.Error(err),
		)
	} else if result != nil {
		d.logger.Debug("Balance result",
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("network", string(network.Name)),
			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) {
	d.logger.Debug("Native balance request",
		zap.String("wallet_ref", wallet.WalletRef),
		zap.String("network", string(network.Name)),
	)
	driverDeps := deps
	driverDeps.Logger = d.logger
	result, err := evm.NativeBalance(ctx, driverDeps, network, wallet, wallet.DepositAddress)
	if err != nil {
		d.logger.Warn("Native balance failed",
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("network", string(network.Name)),
			zap.Error(err),
		)
	} else if result != nil {
		d.logger.Debug("Native balance result",
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("network", string(network.Name)),
			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) {
	d.logger.Debug("Estimate fee request",
		zap.String("wallet_ref", wallet.WalletRef),
		zap.String("network", string(network.Name)),
		zap.String("destination", destination),
	)
	driverDeps := deps
	driverDeps.Logger = d.logger
	result, err := evm.EstimateFee(ctx, driverDeps, network, wallet, wallet.DepositAddress, destination, amount)
	if err != nil {
		d.logger.Warn("Estimate fee failed",
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("network", string(network.Name)),
			zap.Error(err),
		)
	} else if result != nil {
		d.logger.Debug("Estimate fee result",
			zap.String("wallet_ref", wallet.WalletRef),
			zap.String("network", string(network.Name)),
			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) {
	d.logger.Debug("Submit transfer request",
		zap.String("transfer_ref", transfer.TransferRef),
		zap.String("network", string(network.Name)),
		zap.String("destination", destination),
	)
	driverDeps := deps
	driverDeps.Logger = d.logger
	txHash, err := evm.SubmitTransfer(ctx, driverDeps, network, transfer, source, source.DepositAddress, destination)
	if err != nil {
		d.logger.Warn("Submit transfer failed",
			zap.String("transfer_ref", transfer.TransferRef),
			zap.String("network", string(network.Name)),
			zap.Error(err),
		)
	} else {
		d.logger.Debug("Submit transfer result",
			zap.String("transfer_ref", transfer.TransferRef),
			zap.String("network", string(network.Name)),
			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("Await confirmation",
		zap.String("tx_hash", txHash),
		zap.String("network", string(network.Name)),
	)
	driverDeps := deps
	driverDeps.Logger = d.logger
	receipt, err := evm.AwaitConfirmation(ctx, driverDeps, network, txHash)
	if err != nil {
		d.logger.Warn("Await confirmation failed",
			zap.String("tx_hash", txHash),
			zap.String("network", string(network.Name)),
			zap.Error(err),
		)
	} else if receipt != nil {
		d.logger.Debug("Await confirmation result",
			zap.String("tx_hash", txHash),
			zap.String("network", string(network.Name)),
			zap.Uint64("block_number", receipt.BlockNumber.Uint64()),
			zap.Uint64("status", receipt.Status),
		)
	}
	return receipt, err
}

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