package transfer

import (
	"context"
	"errors"
	"strings"

	"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver"
	"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
	"github.com/tech/sendico/pkg/api/routers/gsresponse"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mservice"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	"go.uber.org/zap"
)

type estimateTransferFeeCommand struct {
	deps Deps
}

func NewEstimateTransfer(deps Deps) *estimateTransferFeeCommand {
	return &estimateTransferFeeCommand{deps: deps}
}

func (c *estimateTransferFeeCommand) Execute(ctx context.Context, req *chainv1.EstimateTransferFeeRequest) gsresponse.Responder[chainv1.EstimateTransferFeeResponse] {
	if err := c.deps.EnsureRepository(ctx); err != nil {
		c.deps.Logger.Warn("Repository unavailable", zap.Error(err))
		return gsresponse.Unavailable[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, err)
	}
	if req == nil {
		c.deps.Logger.Warn("Empty request received")
		return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("request is required"))
	}

	sourceWalletRef := strings.TrimSpace(req.GetSourceWalletRef())
	if sourceWalletRef == "" {
		c.deps.Logger.Warn("Source wallet ref missing")
		return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("source_wallet_ref is required"))
	}
	amount := req.GetAmount()
	if amount == nil || strings.TrimSpace(amount.GetAmount()) == "" || strings.TrimSpace(amount.GetCurrency()) == "" {
		c.deps.Logger.Warn("Amount missing or incomplete")
		return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("amount is required"))
	}

	sourceWallet, err := c.deps.Storage.Wallets().Get(ctx, sourceWalletRef)
	if err != nil {
		if errors.Is(err, merrors.ErrNoData) {
			c.deps.Logger.Warn("Source wallet not found", zap.String("source_wallet_ref", sourceWalletRef))
			return gsresponse.NotFound[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, err)
		}
		c.deps.Logger.Warn("Storage get wallet failed", zap.Error(err), zap.String("source_wallet_ref", sourceWalletRef))
		return gsresponse.Auto[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, err)
	}

	networkCfg, ok := c.deps.Networks.Network(sourceWallet.Network)
	if !ok {
		c.deps.Logger.Warn("Unsupported chain", zap.String("network", string(sourceWallet.Network)))
		return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain for wallet"))
	}
	if c.deps.Drivers == nil {
		c.deps.Logger.Warn("Chain drivers missing", zap.String("network", string(sourceWallet.Network)))
		return gsresponse.Internal[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, merrors.Internal("chain drivers not configured"))
	}
	chainDriver, err := c.deps.Drivers.Driver(sourceWallet.Network)
	if err != nil {
		c.deps.Logger.Warn("Unsupported chain driver", zap.String("network", string(sourceWallet.Network)), zap.Error(err))
		return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain for wallet"))
	}

	dest, err := resolveDestination(ctx, c.deps, req.GetDestination(), sourceWallet)
	if err != nil {
		if errors.Is(err, merrors.ErrNoData) {
			c.deps.Logger.Warn("Destination not found", zap.String("destination_wallet_ref", req.GetDestination().GetManagedWalletRef()))
			return gsresponse.NotFound[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, err)
		}
		c.deps.Logger.Warn("Invalid destination", zap.Error(err))
		return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, err)
	}

	destinationAddress, err := destinationAddress(ctx, c.deps, chainDriver, dest)
	if err != nil {
		c.deps.Logger.Warn("Failed to resolve destination address", zap.Error(err))
		return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, err)
	}

	walletForFee := sourceWallet
	nativeCurrency := shared.NativeCurrency(networkCfg)
	if nativeCurrency != "" && strings.EqualFold(nativeCurrency, amount.GetCurrency()) {
		copyWallet := *sourceWallet
		copyWallet.ContractAddress = ""
		copyWallet.TokenSymbol = nativeCurrency
		walletForFee = &copyWallet
	}

	driverDeps := driver.Deps{
		Logger:     c.deps.Logger,
		Registry:   c.deps.Networks,
		RPCTimeout: c.deps.RPCTimeout,
	}
	feeMoney, err := chainDriver.EstimateFee(ctx, driverDeps, networkCfg, walletForFee, destinationAddress, amount)
	if err != nil {
		c.deps.Logger.Warn("Fee estimation failed", zap.Error(err))
		return gsresponse.Auto[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, err)
	}

	contextLabel := "erc20_transfer"
	if strings.TrimSpace(walletForFee.ContractAddress) == "" {
		contextLabel = "native_transfer"
	}
	resp := &chainv1.EstimateTransferFeeResponse{
		NetworkFee:        feeMoney,
		EstimationContext: contextLabel,
	}
	return gsresponse.Success(resp)
}
