package quotation

import (
	"context"
	"errors"
	"github.com/tech/sendico/pkg/discovery"
	"reflect"
	"testing"

	chainclient "github.com/tech/sendico/gateway/chain/client"
	"github.com/tech/sendico/payments/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func TestManagedWalletNetworkResolver_ResolvesAcrossDiscoveredGateways(t *testing.T) {
	invokeResolver := &fakeGatewayInvokeResolver{
		clients: map[string]chainclient.Client{
			"gw-a:50053": &chainclient.Fake{
				GetManagedWalletFn: func(context.Context, *chainv1.GetManagedWalletRequest) (*chainv1.GetManagedWalletResponse, error) {
					return nil, status.Error(codes.NotFound, "not_found")
				},
			},
			"gw-b:50053": &chainclient.Fake{
				GetManagedWalletFn: func(context.Context, *chainv1.GetManagedWalletRequest) (*chainv1.GetManagedWalletResponse, error) {
					return &chainv1.GetManagedWalletResponse{
						Wallet: &chainv1.ManagedWallet{
							Asset: &chainv1.Asset{Chain: chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE},
						},
					}, nil
				},
			},
		},
	}
	resolver := &managedWalletNetworkResolver{
		gatewayRegistry: fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{ID: "gw-a", Rail: discovery.RailCrypto, IsEnabled: true, InvokeURI: "gw-a:50053"},
				{ID: "gw-b", Rail: discovery.RailCrypto, IsEnabled: true, InvokeURI: "gw-b:50053"},
			},
		},
		gatewayInvokeResolver: invokeResolver,
		logger:                zap.NewNop(),
	}

	network, err := resolver.ResolveManagedWalletNetwork(context.Background(), "wallet-ref")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got, want := network, "TRON_NILE"; got != want {
		t.Fatalf("network mismatch: got=%q want=%q", got, want)
	}
	if got, want := invokeResolver.calls, []string{"gw-a:50053", "gw-b:50053"}; !reflect.DeepEqual(got, want) {
		t.Fatalf("invoke calls mismatch: got=%v want=%v", got, want)
	}
}

func TestManagedWalletNetworkResolver_FallbacksToChainResolver(t *testing.T) {
	chainResolver := &fakeChainResolver{
		client: &chainclient.Fake{
			GetManagedWalletFn: func(context.Context, *chainv1.GetManagedWalletRequest) (*chainv1.GetManagedWalletResponse, error) {
				return &chainv1.GetManagedWalletResponse{
					Wallet: &chainv1.ManagedWallet{
						Asset: &chainv1.Asset{Chain: chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_SEPOLIA},
					},
				}, nil
			},
		},
	}
	resolver := &managedWalletNetworkResolver{
		resolver: chainResolver,
		logger:   zap.NewNop(),
	}

	network, err := resolver.ResolveManagedWalletNetwork(context.Background(), "wallet-ref")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got, want := network, "ARBITRUM_SEPOLIA"; got != want {
		t.Fatalf("network mismatch: got=%q want=%q", got, want)
	}
	if got, want := chainResolver.args, []string{""}; !reflect.DeepEqual(got, want) {
		t.Fatalf("resolver args mismatch: got=%v want=%v", got, want)
	}
}

func TestManagedWalletNetworkResolver_ReturnsNoDataWhenNoGateways(t *testing.T) {
	resolver := &managedWalletNetworkResolver{
		gatewayRegistry: fakeGatewayRegistry{},
		gatewayInvokeResolver: &fakeGatewayInvokeResolver{
			clients: map[string]chainclient.Client{},
		},
		logger: zap.NewNop(),
	}

	_, err := resolver.ResolveManagedWalletNetwork(context.Background(), "wallet-ref")
	if !errors.Is(err, merrors.ErrNoData) {
		t.Fatalf("expected no_data error, got %v", err)
	}
}

type fakeGatewayRegistry struct {
	items []*model.GatewayInstanceDescriptor
	err   error
}

func (f fakeGatewayRegistry) List(context.Context) ([]*model.GatewayInstanceDescriptor, error) {
	return f.items, f.err
}

type fakeGatewayInvokeResolver struct {
	clients map[string]chainclient.Client
	err     error
	calls   []string
}

func (f *fakeGatewayInvokeResolver) Resolve(_ context.Context, invokeURI string) (chainclient.Client, error) {
	f.calls = append(f.calls, invokeURI)
	if f.err != nil {
		return nil, f.err
	}
	return f.clients[invokeURI], nil
}

type fakeChainResolver struct {
	client chainclient.Client
	err    error
	args   []string
}

func (f *fakeChainResolver) Resolve(_ context.Context, network string) (chainclient.Client, error) {
	f.args = append(f.args, network)
	if f.err != nil {
		return nil, f.err
	}
	return f.client, nil
}
