package transfer

import (
	"context"
	"errors"
	"strings"

	"github.com/shopspring/decimal"
	"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
	"github.com/tech/sendico/gateway/chain/storage/model"
	"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 submitTransferCommand struct {
	deps Deps
}

func NewSubmitTransfer(deps Deps) *submitTransferCommand {
	return &submitTransferCommand{deps: deps}
}

func (c *submitTransferCommand) Execute(ctx context.Context, req *chainv1.SubmitTransferRequest) gsresponse.Responder[chainv1.SubmitTransferResponse] {
	if err := c.deps.EnsureRepository(ctx); err != nil {
		c.deps.Logger.Warn("Repository unavailable", zap.Error(err))
		return gsresponse.Unavailable[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, err)
	}
	if req == nil {
		c.deps.Logger.Warn("Nil request")
		return gsresponse.InvalidArgument[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("nil request"))
	}

	idempotencyKey := strings.TrimSpace(req.GetIdempotencyKey())
	if idempotencyKey == "" {
		c.deps.Logger.Warn("Missing idempotency key")
		return gsresponse.InvalidArgument[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("idempotency_key is required"))
	}
	organizationRef := strings.TrimSpace(req.GetOrganizationRef())
	if organizationRef == "" {
		c.deps.Logger.Warn("Missing organization ref")
		return gsresponse.InvalidArgument[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("organization_ref is required"))
	}
	sourceWalletRef := strings.TrimSpace(req.GetSourceWalletRef())
	if sourceWalletRef == "" {
		c.deps.Logger.Warn("Missing source wallet ref")
		return gsresponse.InvalidArgument[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("source_wallet_ref is required"))
	}
	amount := req.GetAmount()
	if amount == nil {
		c.deps.Logger.Warn("Missing amount")
		return gsresponse.InvalidArgument[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("amount is required"))
	}
	amountCurrency := strings.ToUpper(strings.TrimSpace(amount.GetCurrency()))
	if amountCurrency == "" {
		c.deps.Logger.Warn("Missing amount currency")
		return gsresponse.InvalidArgument[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("amount.currency is required"))
	}
	amountValue := strings.TrimSpace(amount.GetAmount())
	if amountValue == "" {
		c.deps.Logger.Warn("Missing amount value")
		return gsresponse.InvalidArgument[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("amount.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.SubmitTransferResponse](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.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, err)
	}
	if !strings.EqualFold(sourceWallet.OrganizationRef, organizationRef) {
		c.deps.Logger.Warn("Organization mismatch", zap.String("wallet_org", sourceWallet.OrganizationRef), zap.String("req_org", organizationRef))
		return gsresponse.InvalidArgument[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("organization_ref mismatch with wallet"))
	}
	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.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain for wallet"))
	}

	destination, 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.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, err)
		}
		c.deps.Logger.Warn("Invalid destination", zap.Error(err))
		return gsresponse.InvalidArgument[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, err)
	}

	fees, feeSum, err := convertFees(req.GetFees(), amountCurrency)
	if err != nil {
		c.deps.Logger.Warn("Fee conversion failed", zap.Error(err))
		return gsresponse.InvalidArgument[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, err)
	}
	amountDec, err := decimal.NewFromString(amountValue)
	if err != nil {
		c.deps.Logger.Warn("Invalid amount", zap.Error(err))
		return gsresponse.InvalidArgument[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("invalid amount"))
	}
	netDec := amountDec.Sub(feeSum)
	if netDec.IsNegative() {
		c.deps.Logger.Warn("Fees exceed amount", zap.String("amount", amountValue), zap.String("fee_sum", feeSum.String()))
		return gsresponse.InvalidArgument[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("fees exceed amount"))
	}

	netAmount := shared.CloneMoney(amount)
	netAmount.Amount = netDec.String()

	effectiveTokenSymbol := sourceWallet.TokenSymbol
	effectiveContractAddress := sourceWallet.ContractAddress
	nativeCurrency := shared.NativeCurrency(networkCfg)
	if nativeCurrency != "" && strings.EqualFold(nativeCurrency, amountCurrency) {
		effectiveTokenSymbol = nativeCurrency
		effectiveContractAddress = ""
	}

	transfer := &model.Transfer{
		IdempotencyKey:  idempotencyKey,
		TransferRef:     shared.GenerateTransferRef(),
		IntentRef:       req.IntentRef,
		OperationRef:    req.OperationRef,
		OrganizationRef: organizationRef,
		SourceWalletRef: sourceWalletRef,
		Destination:     destination,
		Network:         sourceWallet.Network,
		TokenSymbol:     effectiveTokenSymbol,
		ContractAddress: effectiveContractAddress,
		RequestedAmount: shared.ProtoToMoney(amount),
		NetAmount:       shared.ProtoToMoney(netAmount),
		Fees:            fees,
		Status:          model.TransferStatusCreated,
		PaymentRef:      strings.TrimSpace(req.GetPaymentRef()),
		LastStatusAt:    c.deps.Clock.Now().UTC(),
	}

	saved, err := c.deps.Storage.Transfers().Create(ctx, transfer)
	if err != nil {
		if errors.Is(err, merrors.ErrDataConflict) {
			c.deps.Logger.Debug("Transfer already exists", zap.String("transfer_ref", transfer.TransferRef), zap.String("idempotency_key", idempotencyKey))
			return gsresponse.Success(&chainv1.SubmitTransferResponse{Transfer: toProtoTransfer(saved)})
		}
		c.deps.Logger.Warn("Storage create failed", zap.Error(err), zap.String("transfer_ref", transfer.TransferRef))
		return gsresponse.Auto[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, err)
	}

	if c.deps.LaunchExecution != nil {
		c.deps.LaunchExecution(saved.TransferRef, sourceWalletRef, networkCfg)
	}

	return gsresponse.Success(&chainv1.SubmitTransferResponse{Transfer: toProtoTransfer(saved)})
}
