package gateway

import (
	"context"
	"fmt"
	"math/big"
	"sort"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	pmodel "github.com/tech/sendico/pkg/model"
	connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
	ichainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/tech/sendico/gateway/chain/internal/keymanager"
	"github.com/tech/sendico/gateway/chain/internal/service/gateway/drivers"
	"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
	"github.com/tech/sendico/gateway/chain/storage"
	"github.com/tech/sendico/gateway/chain/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"

	"github.com/ethereum/go-ethereum/core/types"
)

const (
	walletDefaultLimit   int64 = 50
	walletMaxLimit       int64 = 200
	transferDefaultLimit int64 = 50
	transferMaxLimit     int64 = 200
	depositDefaultLimit  int64 = 100
	depositMaxLimit      int64 = 500
)

func TestCreateManagedWallet_Idempotent(t *testing.T) {
	svc, repo := newTestService(t)

	ctx := context.Background()
	req := &ichainv1.CreateManagedWalletRequest{
		IdempotencyKey:  "idem-1",
		OrganizationRef: "org-1",
		OwnerRef:        "owner-1",
		Asset: &ichainv1.Asset{
			Chain:       ichainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET,
			TokenSymbol: "USDC",
		},
	}

	resp, err := svc.CreateManagedWallet(ctx, req)
	require.NoError(t, err)
	require.NotNil(t, resp.GetWallet())
	firstRef := resp.GetWallet().GetWalletRef()
	require.NotEmpty(t, firstRef)

	resp2, err := svc.CreateManagedWallet(ctx, req)
	require.NoError(t, err)
	require.Equal(t, firstRef, resp2.GetWallet().GetWalletRef())

	// ensure stored only once
	require.Equal(t, 1, repo.wallets.count())
}

func TestCreateManagedWallet_NativeTokenWithoutContract(t *testing.T) {
	svc, _ := newTestService(t)
	ctx := context.Background()

	resp, err := svc.CreateManagedWallet(ctx, &ichainv1.CreateManagedWalletRequest{
		IdempotencyKey:  "idem-native",
		OrganizationRef: "org-1",
		OwnerRef:        "owner-1",
		Asset: &ichainv1.Asset{
			Chain:       ichainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET,
			TokenSymbol: "ETH",
		},
	})
	require.NoError(t, err)
	require.NotNil(t, resp.GetWallet())
	require.Equal(t, "ETH", resp.GetWallet().GetAsset().GetTokenSymbol())
	require.Empty(t, resp.GetWallet().GetAsset().GetContractAddress())
}

func TestListAccounts_OrganizationRefFilters(t *testing.T) {
	svc, _ := newTestService(t)
	ctx := context.Background()

	_, err := svc.CreateManagedWallet(ctx, &ichainv1.CreateManagedWalletRequest{
		IdempotencyKey:  "idem-org-1",
		OrganizationRef: "org-1",
		OwnerRef:        "owner-1",
		Asset: &ichainv1.Asset{
			Chain:       ichainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET,
			TokenSymbol: "USDC",
		},
	})
	require.NoError(t, err)

	_, err = svc.CreateManagedWallet(ctx, &ichainv1.CreateManagedWalletRequest{
		IdempotencyKey:  "idem-org-2",
		OrganizationRef: "org-2",
		OwnerRef:        "owner-2",
		Asset: &ichainv1.Asset{
			Chain:       ichainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET,
			TokenSymbol: "USDC",
		},
	})
	require.NoError(t, err)

	resp, err := svc.ListAccounts(ctx, &connectorv1.ListAccountsRequest{
		OrganizationRef: "org-1",
		Kind:            connectorv1.AccountKind_CHAIN_MANAGED_WALLET,
	})
	require.NoError(t, err)
	require.Len(t, resp.GetAccounts(), 1)

	details := resp.GetAccounts()[0].GetProviderDetails()
	require.NotNil(t, details)
	orgField := details.GetFields()["organization_ref"]
	require.NotNil(t, orgField)
	require.Equal(t, "org-1", orgField.GetStringValue())
}

func TestListAccounts_NoWalletsNotError(t *testing.T) {
	baseRepo := newInMemoryRepository()
	svc := newTestServiceWithRepository(t, &walletsNoDataRepository{inMemoryRepository: baseRepo})
	ctx := context.Background()

	resp, err := svc.ListAccounts(ctx, &connectorv1.ListAccountsRequest{
		OrganizationRef: "org-1",
		Kind:            connectorv1.AccountKind_CHAIN_MANAGED_WALLET,
	})
	require.NoError(t, err)
	require.NotNil(t, resp)
	require.Empty(t, resp.GetAccounts())
}

func TestSubmitTransfer_ManagedDestination(t *testing.T) {
	svc, repo := newTestService(t)
	ctx := context.Background()

	// create source wallet
	srcResp, err := svc.CreateManagedWallet(ctx, &ichainv1.CreateManagedWalletRequest{
		IdempotencyKey:  "idem-src",
		OrganizationRef: "org-1",
		OwnerRef:        "owner-1",
		Asset: &ichainv1.Asset{
			Chain:       ichainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET,
			TokenSymbol: "USDC",
		},
	})
	require.NoError(t, err)
	srcRef := srcResp.GetWallet().GetWalletRef()

	// destination wallet
	dstResp, err := svc.CreateManagedWallet(ctx, &ichainv1.CreateManagedWalletRequest{
		IdempotencyKey:  "idem-dst",
		OrganizationRef: "org-1",
		OwnerRef:        "owner-2",
		Asset: &ichainv1.Asset{
			Chain:       ichainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET,
			TokenSymbol: "USDC",
		},
	})
	require.NoError(t, err)
	dstRef := dstResp.GetWallet().GetWalletRef()

	transferResp, err := svc.SubmitTransfer(ctx, &ichainv1.SubmitTransferRequest{
		IdempotencyKey:  "transfer-1",
		OrganizationRef: "org-1",
		SourceWalletRef: srcRef,
		Destination: &ichainv1.TransferDestination{
			Destination: &ichainv1.TransferDestination_ManagedWalletRef{ManagedWalletRef: dstRef},
		},
		Amount: &moneyv1.Money{Currency: "USDC", Amount: "100"},
		Fees: []*ichainv1.ServiceFeeBreakdown{
			{
				FeeCode: "service",
				Amount:  &moneyv1.Money{Currency: "USDC", Amount: "5"},
			},
		},
		IntentRef: "intent-1",
	})
	require.NoError(t, err)
	require.NotNil(t, transferResp.GetTransfer())
	require.Equal(t, "95", transferResp.GetTransfer().GetNetAmount().GetAmount())

	stored := repo.transfers.get(transferResp.GetTransfer().GetTransferRef())
	require.NotNil(t, stored)
	require.Equal(t, model.TransferStatusCreated, stored.Status)

	// GetTransfer
	getResp, err := svc.GetTransfer(ctx, &ichainv1.GetTransferRequest{TransferRef: stored.TransferRef})
	require.NoError(t, err)
	require.Equal(t, stored.TransferRef, getResp.GetTransfer().GetTransferRef())

	// ListTransfers
	listResp, err := svc.ListTransfers(ctx, &ichainv1.ListTransfersRequest{
		SourceWalletRef: srcRef,
		Page:            &paginationv1.CursorPageRequest{Limit: 10},
	})
	require.NoError(t, err)
	require.Len(t, listResp.GetTransfers(), 1)
	require.Equal(t, stored.TransferRef, listResp.GetTransfers()[0].GetTransferRef())
}

func TestGetWalletBalance_NotFound(t *testing.T) {
	svc, _ := newTestService(t)
	ctx := context.Background()

	_, err := svc.GetWalletBalance(ctx, &ichainv1.GetWalletBalanceRequest{WalletRef: "missing"})
	require.Error(t, err)
	st, _ := status.FromError(err)
	require.Equal(t, codes.NotFound, st.Code())
}

func TestGetWalletBalance_ReturnsCachedNativeAvailable(t *testing.T) {
	svc, repo := newTestService(t)
	ctx := context.Background()

	createResp, err := svc.CreateManagedWallet(ctx, &ichainv1.CreateManagedWalletRequest{
		IdempotencyKey:  "idem-balance",
		OrganizationRef: "org-1",
		OwnerRef:        "owner-1",
		Asset: &ichainv1.Asset{
			Chain:       ichainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET,
			TokenSymbol: "USDC",
		},
	})
	require.NoError(t, err)
	walletRef := createResp.GetWallet().GetWalletRef()

	err = repo.wallets.SaveBalance(ctx, &model.WalletBalance{
		WalletRef:       walletRef,
		Available:       &moneyv1.Money{Currency: "USDC", Amount: "25"},
		NativeAvailable: &moneyv1.Money{Currency: "ETH", Amount: "0.5"},
		CalculatedAt:    time.Now().UTC(),
	})
	require.NoError(t, err)

	resp, err := svc.GetWalletBalance(ctx, &ichainv1.GetWalletBalanceRequest{WalletRef: walletRef})
	require.NoError(t, err)
	require.NotNil(t, resp.GetBalance())
	require.Equal(t, "0.5", resp.GetBalance().GetNativeAvailable().GetAmount())
	require.Equal(t, "ETH", resp.GetBalance().GetNativeAvailable().GetCurrency())
}

// ---- in-memory storage implementation ----

type inMemoryRepository struct {
	wallets   *inMemoryWallets
	transfers *inMemoryTransfers
	deposits  *inMemoryDeposits
}

func newInMemoryRepository() *inMemoryRepository {
	return &inMemoryRepository{
		wallets:   newInMemoryWallets(),
		transfers: newInMemoryTransfers(),
		deposits:  newInMemoryDeposits(),
	}
}

type walletsNoDataRepository struct {
	*inMemoryRepository
}

func (r *walletsNoDataRepository) Wallets() storage.WalletsStore {
	return &walletsNoDataStore{WalletsStore: r.wallets}
}

type walletsNoDataStore struct {
	storage.WalletsStore
}

func (w *walletsNoDataStore) List(context.Context, model.ManagedWalletFilter) (*model.ManagedWalletList, error) {
	return nil, merrors.NoData("no wallets")
}

func (r *inMemoryRepository) Ping(context.Context) error        { return nil }
func (r *inMemoryRepository) Wallets() storage.WalletsStore     { return r.wallets }
func (r *inMemoryRepository) Transfers() storage.TransfersStore { return r.transfers }
func (r *inMemoryRepository) Deposits() storage.DepositsStore   { return r.deposits }

// Wallets store

type inMemoryWallets struct {
	mu       sync.Mutex
	wallets  map[string]*model.ManagedWallet
	byIdemp  map[string]string
	balances map[string]*model.WalletBalance
}

func newInMemoryWallets() *inMemoryWallets {
	return &inMemoryWallets{
		wallets:  make(map[string]*model.ManagedWallet),
		byIdemp:  make(map[string]string),
		balances: make(map[string]*model.WalletBalance),
	}
}

func (w *inMemoryWallets) count() int {
	w.mu.Lock()
	defer w.mu.Unlock()
	return len(w.wallets)
}

func (w *inMemoryWallets) Create(ctx context.Context, wallet *model.ManagedWallet) (*model.ManagedWallet, error) {
	w.mu.Lock()
	defer w.mu.Unlock()

	if wallet == nil {
		return nil, merrors.InvalidArgument("walletsStore: nil wallet")
	}
	wallet.Normalize()
	if wallet.IdempotencyKey == "" {
		return nil, merrors.InvalidArgument("walletsStore: empty idempotencyKey")
	}

	if existingRef, ok := w.byIdemp[wallet.IdempotencyKey]; ok {
		existing := w.wallets[existingRef]
		return existing, merrors.ErrDataConflict
	}

	if wallet.WalletRef == "" {
		wallet.WalletRef = bson.NewObjectID().Hex()
	}
	if wallet.GetID() == nil || wallet.GetID().IsZero() {
		wallet.SetID(bson.NewObjectID())
	} else {
		wallet.Update()
	}

	w.wallets[wallet.WalletRef] = wallet
	w.byIdemp[wallet.IdempotencyKey] = wallet.WalletRef
	return wallet, nil
}

func (w *inMemoryWallets) Get(ctx context.Context, walletRef string) (*model.ManagedWallet, error) {
	w.mu.Lock()
	defer w.mu.Unlock()
	wallet, ok := w.wallets[strings.TrimSpace(walletRef)]
	if !ok {
		return nil, merrors.NoData("wallet not found")
	}
	return wallet, nil
}

func (w *inMemoryWallets) List(ctx context.Context, filter model.ManagedWalletFilter) (*model.ManagedWalletList, error) {
	w.mu.Lock()
	defer w.mu.Unlock()

	items := make([]model.ManagedWallet, 0, len(w.wallets))
	for _, wallet := range w.wallets {
		if filter.OrganizationRef != "" && !strings.EqualFold(wallet.OrganizationRef, filter.OrganizationRef) {
			continue
		}
		if filter.OwnerRefFilter != nil {
			ownerRef := strings.TrimSpace(*filter.OwnerRefFilter)
			if !strings.EqualFold(wallet.OwnerRef, ownerRef) {
				continue
			}
		}
		if filter.Network != "" && wallet.Network != filter.Network {
			continue
		}
		if filter.TokenSymbol != "" && !strings.EqualFold(wallet.TokenSymbol, filter.TokenSymbol) {
			continue
		}
		items = append(items, *wallet)
	}

	sort.Slice(items, func(i, j int) bool {
		return items[i].ID.Timestamp().Before(items[j].ID.Timestamp())
	})

	startIndex := 0
	if cursor := strings.TrimSpace(filter.Cursor); cursor != "" {
		if oid, err := bson.ObjectIDFromHex(cursor); err == nil {
			for idx, item := range items {
				if item.ID.Timestamp().After(oid.Timestamp()) {
					startIndex = idx
					break
				}
			}
		}
	}

	limit := int(sanitizeLimit(filter.Limit, walletDefaultLimit, walletMaxLimit))
	end := startIndex + limit
	hasMore := false
	if end < len(items) {
		hasMore = true
		items = items[startIndex:end]
	} else {
		items = items[startIndex:]
	}

	nextCursor := ""
	if hasMore && len(items) > 0 {
		nextCursor = items[len(items)-1].ID.Hex()
	}

	return &model.ManagedWalletList{Items: items, NextCursor: nextCursor}, nil
}

func (w *inMemoryWallets) SaveBalance(ctx context.Context, balance *model.WalletBalance) error {
	w.mu.Lock()
	defer w.mu.Unlock()
	if balance == nil {
		return merrors.InvalidArgument("walletsStore: nil balance")
	}
	balance.Normalize()
	if balance.WalletRef == "" {
		return merrors.InvalidArgument("walletsStore: empty walletRef for balance")
	}
	if balance.CalculatedAt.IsZero() {
		balance.CalculatedAt = time.Now().UTC()
	}
	existing, ok := w.balances[balance.WalletRef]
	if !ok {
		if balance.GetID() == nil || balance.GetID().IsZero() {
			balance.SetID(bson.NewObjectID())
		}
		w.balances[balance.WalletRef] = balance
		return nil
	}
	existing.Available = balance.Available
	existing.PendingInbound = balance.PendingInbound
	existing.PendingOutbound = balance.PendingOutbound
	existing.CalculatedAt = balance.CalculatedAt
	existing.Update()
	return nil
}

func (w *inMemoryWallets) GetBalance(ctx context.Context, walletRef string) (*model.WalletBalance, error) {
	w.mu.Lock()
	defer w.mu.Unlock()
	balance, ok := w.balances[strings.TrimSpace(walletRef)]
	if !ok {
		return nil, merrors.NoData("wallet balance not found")
	}
	return balance, nil
}

// Transfers store

type inMemoryTransfers struct {
	mu      sync.Mutex
	items   map[string]*model.Transfer
	byIdemp map[string]string
}

func newInMemoryTransfers() *inMemoryTransfers {
	return &inMemoryTransfers{
		items:   make(map[string]*model.Transfer),
		byIdemp: make(map[string]string),
	}
}

func (t *inMemoryTransfers) Create(ctx context.Context, transfer *model.Transfer) (*model.Transfer, error) {
	t.mu.Lock()
	defer t.mu.Unlock()
	if transfer == nil {
		return nil, merrors.InvalidArgument("transfersStore: nil transfer")
	}
	transfer.Normalize()
	if transfer.IdempotencyKey == "" {
		return nil, merrors.InvalidArgument("transfersStore: empty idempotencyKey")
	}
	if ref, ok := t.byIdemp[transfer.IdempotencyKey]; ok {
		return t.items[ref], merrors.ErrDataConflict
	}
	if transfer.TransferRef == "" {
		transfer.TransferRef = bson.NewObjectID().Hex()
	}
	if transfer.GetID() == nil || transfer.GetID().IsZero() {
		transfer.SetID(bson.NewObjectID())
	} else {
		transfer.Update()
	}
	t.items[transfer.TransferRef] = transfer
	t.byIdemp[transfer.IdempotencyKey] = transfer.TransferRef
	return transfer, nil
}

func (t *inMemoryTransfers) Get(ctx context.Context, transferRef string) (*model.Transfer, error) {
	t.mu.Lock()
	defer t.mu.Unlock()
	transfer, ok := t.items[strings.TrimSpace(transferRef)]
	if !ok {
		return nil, merrors.NoData("transfer not found")
	}
	return transfer, nil
}

func (t *inMemoryTransfers) FindByOperationRef(ctx context.Context, organizationRef, operationRef string) (*model.Transfer, error) {
	t.mu.Lock()
	defer t.mu.Unlock()

	org := strings.TrimSpace(organizationRef)
	opRef := strings.TrimSpace(operationRef)
	if opRef == "" {
		return nil, merrors.InvalidArgument("transfersStore: empty operationRef")
	}

	for _, transfer := range t.items {
		if transfer == nil {
			continue
		}
		if !strings.EqualFold(strings.TrimSpace(transfer.OperationRef), opRef) {
			continue
		}
		if org != "" && !strings.EqualFold(strings.TrimSpace(transfer.OrganizationRef), org) {
			continue
		}
		return transfer, nil
	}

	return nil, merrors.NoData("transfer not found")
}

func (t *inMemoryTransfers) List(ctx context.Context, filter model.TransferFilter) (*model.TransferList, error) {
	t.mu.Lock()
	defer t.mu.Unlock()
	items := make([]*model.Transfer, 0, len(t.items))
	for _, transfer := range t.items {
		if filter.SourceWalletRef != "" && !strings.EqualFold(transfer.SourceWalletRef, filter.SourceWalletRef) {
			continue
		}
		if filter.DestinationWalletRef != "" && !strings.EqualFold(transfer.Destination.ManagedWalletRef, filter.DestinationWalletRef) {
			continue
		}
		if filter.Status != "" && transfer.Status != filter.Status {
			continue
		}
		items = append(items, transfer)
	}

	sort.Slice(items, func(i, j int) bool {
		return items[i].ID.Timestamp().Before(items[j].ID.Timestamp())
	})

	start := 0
	if cursor := strings.TrimSpace(filter.Cursor); cursor != "" {
		if oid, err := bson.ObjectIDFromHex(cursor); err == nil {
			for idx, item := range items {
				if item.ID.Timestamp().After(oid.Timestamp()) {
					start = idx
					break
				}
			}
		}
	}

	limit := int(sanitizeLimit(filter.Limit, transferDefaultLimit, transferMaxLimit))
	end := start + limit
	hasMore := false
	if end < len(items) {
		hasMore = true
		items = items[start:end]
	} else {
		items = items[start:]
	}

	nextCursor := ""
	if hasMore && len(items) > 0 {
		nextCursor = items[len(items)-1].ID.Hex()
	}

	return &model.TransferList{Items: items, NextCursor: nextCursor}, nil
}

func (t *inMemoryTransfers) UpdateStatus(ctx context.Context, transferRef string, status model.TransferStatus, failureReason string, txHash string) (*model.Transfer, error) {
	t.mu.Lock()
	defer t.mu.Unlock()
	transfer, ok := t.items[strings.TrimSpace(transferRef)]
	if !ok {
		return nil, merrors.NoData("transfer not found")
	}
	transfer.Status = status
	if status == model.TransferStatusFailed {
		transfer.FailureReason = strings.TrimSpace(failureReason)
	} else {
		transfer.FailureReason = ""
	}
	transfer.TxHash = strings.TrimSpace(txHash)
	transfer.LastStatusAt = time.Now().UTC()
	transfer.Update()
	return transfer, nil
}

// helper for tests
func (t *inMemoryTransfers) get(ref string) *model.Transfer {
	t.mu.Lock()
	defer t.mu.Unlock()
	return t.items[ref]
}

// Deposits store (minimal for tests)

type inMemoryDeposits struct {
	mu    sync.Mutex
	items map[string]*model.Deposit
}

func newInMemoryDeposits() *inMemoryDeposits {
	return &inMemoryDeposits{items: make(map[string]*model.Deposit)}
}

func (d *inMemoryDeposits) Record(ctx context.Context, deposit *model.Deposit) error {
	d.mu.Lock()
	defer d.mu.Unlock()
	if deposit == nil {
		return merrors.InvalidArgument("depositsStore: nil deposit")
	}
	deposit.Normalize()
	if deposit.DepositRef == "" {
		return merrors.InvalidArgument("depositsStore: empty depositRef")
	}
	if existing, ok := d.items[deposit.DepositRef]; ok {
		existing.Status = deposit.Status
		existing.LastStatusAt = time.Now().UTC()
		existing.Update()
		return nil
	}
	if deposit.GetID() == nil || deposit.GetID().IsZero() {
		deposit.SetID(bson.NewObjectID())
	}
	if deposit.ObservedAt.IsZero() {
		deposit.ObservedAt = time.Now().UTC()
	}
	if deposit.RecordedAt.IsZero() {
		deposit.RecordedAt = time.Now().UTC()
	}
	deposit.LastStatusAt = time.Now().UTC()
	d.items[deposit.DepositRef] = deposit
	return nil
}

func (d *inMemoryDeposits) ListPending(ctx context.Context, network string, limit int32) ([]*model.Deposit, error) {
	d.mu.Lock()
	defer d.mu.Unlock()
	results := make([]*model.Deposit, 0)
	for _, deposit := range d.items {
		if deposit.Status != model.DepositStatusPending {
			continue
		}
		if network != "" && !strings.EqualFold(deposit.Network, network) {
			continue
		}
		results = append(results, deposit)
	}
	sort.Slice(results, func(i, j int) bool {
		return results[i].ObservedAt.Before(results[j].ObservedAt)
	})
	limitVal := int(sanitizeLimit(limit, depositDefaultLimit, depositMaxLimit))
	if len(results) > limitVal {
		results = results[:limitVal]
	}
	return results, nil
}

// shared helpers

func sanitizeLimit(requested int32, def, max int64) int64 {
	if requested <= 0 {
		return def
	}
	requested64 := int64(requested)
	if requested64 > max {
		return max
	}
	return requested64
}

func newTestService(t *testing.T) (*Service, *inMemoryRepository) {
	repo := newInMemoryRepository()
	svc := newTestServiceWithRepository(t, repo)
	return svc, repo
}

func newTestServiceWithRepository(t *testing.T, repo storage.Repository) *Service {
	logger := zap.NewNop()
	networks := []shared.Network{{
		Name:        "ethereum_mainnet",
		NativeToken: "ETH",
		TokenConfigs: []shared.TokenContract{
			{Symbol: "USDC", ContractAddress: "0xusdc"},
		},
	}}
	driverRegistry, err := drivers.NewRegistry(logger.Named("drivers"), networks)
	require.NoError(t, err)
	svc := NewService(logger, repo, nil,
		WithKeyManager(&fakeKeyManager{}),
		WithNetworks(networks),
		WithServiceWallet(shared.ServiceWallet{Network: "ethereum_mainnet", Address: "0xservice"}),
		WithDriverRegistry(driverRegistry),
	)
	return svc
}

type fakeKeyManager struct{}

func (f *fakeKeyManager) CreateManagedWalletKey(ctx context.Context, walletRef string, network pmodel.ChainNetwork) (*keymanager.ManagedWalletKey, error) {
	return &keymanager.ManagedWalletKey{
		KeyID:     fmt.Sprintf("%s/%s", network, walletRef),
		Address:   "0x" + strings.Repeat("a", 40),
		PublicKey: strings.Repeat("b", 128),
	}, nil
}

func (f *fakeKeyManager) SignTransaction(ctx context.Context, keyID string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
	return tx, nil
}
