package client

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/types/known/wrapperspb"
)

type stubConnectorClient struct {
	listReq *connectorv1.ListAccountsRequest
	getReq  *connectorv1.GetOperationRequest

	submitReq  *connectorv1.SubmitOperationRequest
	submitResp *connectorv1.SubmitOperationResponse
	getResp    *connectorv1.GetOperationResponse
}

func (s *stubConnectorClient) GetCapabilities(ctx context.Context, in *connectorv1.GetCapabilitiesRequest, opts ...grpc.CallOption) (*connectorv1.GetCapabilitiesResponse, error) {
	return &connectorv1.GetCapabilitiesResponse{}, nil
}

func (s *stubConnectorClient) OpenAccount(ctx context.Context, in *connectorv1.OpenAccountRequest, opts ...grpc.CallOption) (*connectorv1.OpenAccountResponse, error) {
	return &connectorv1.OpenAccountResponse{}, nil
}

func (s *stubConnectorClient) GetAccount(ctx context.Context, in *connectorv1.GetAccountRequest, opts ...grpc.CallOption) (*connectorv1.GetAccountResponse, error) {
	return &connectorv1.GetAccountResponse{}, nil
}

func (s *stubConnectorClient) ListAccounts(ctx context.Context, in *connectorv1.ListAccountsRequest, opts ...grpc.CallOption) (*connectorv1.ListAccountsResponse, error) {
	s.listReq = in
	return &connectorv1.ListAccountsResponse{}, nil
}

func (s *stubConnectorClient) GetBalance(ctx context.Context, in *connectorv1.GetBalanceRequest, opts ...grpc.CallOption) (*connectorv1.GetBalanceResponse, error) {
	return &connectorv1.GetBalanceResponse{}, nil
}

func (s *stubConnectorClient) SubmitOperation(ctx context.Context, in *connectorv1.SubmitOperationRequest, opts ...grpc.CallOption) (*connectorv1.SubmitOperationResponse, error) {
	s.submitReq = in
	if s.submitResp != nil {
		return s.submitResp, nil
	}
	return &connectorv1.SubmitOperationResponse{}, nil
}

func (s *stubConnectorClient) GetOperation(ctx context.Context, in *connectorv1.GetOperationRequest, opts ...grpc.CallOption) (*connectorv1.GetOperationResponse, error) {
	s.getReq = in
	if s.getResp != nil {
		return s.getResp, nil
	}
	return &connectorv1.GetOperationResponse{}, nil
}

func (s *stubConnectorClient) ListOperations(ctx context.Context, in *connectorv1.ListOperationsRequest, opts ...grpc.CallOption) (*connectorv1.ListOperationsResponse, error) {
	return &connectorv1.ListOperationsResponse{}, nil
}

func TestListManagedWallets_ForwardsOrganizationRef(t *testing.T) {
	stub := &stubConnectorClient{}
	client := NewWithClient(Config{}, stub)

	_, err := client.ListManagedWallets(context.Background(), &chainv1.ListManagedWalletsRequest{
		OrganizationRef: "org-1",
		OwnerRefFilter:  wrapperspb.String("owner-1"),
		Asset: &chainv1.Asset{
			Chain:       chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET,
			TokenSymbol: "USDC",
		},
	})
	require.NoError(t, err)
	require.NotNil(t, stub.listReq)
	require.Equal(t, "org-1", stub.listReq.GetOrganizationRef())
	require.Equal(t, "owner-1", stub.listReq.GetOwnerRefFilter().GetValue())
	require.Equal(t, connectorv1.AccountKind_CHAIN_MANAGED_WALLET, stub.listReq.GetKind())
}

func TestTransferFromReceipt_PreservesRequestReferences(t *testing.T) {
	req := &chainv1.SubmitTransferRequest{
		IdempotencyKey:  "idem-1",
		OrganizationRef: "org-1",
		SourceWalletRef: "wallet-src",
		Amount:          &moneyv1.Money{Amount: "12.34", Currency: "USDT"},
		IntentRef:       "intent-1",
		OperationRef:    "payment-1:hop_1_crypto_send",
		PaymentRef:      "payment-1",
	}
	receipt := &connectorv1.OperationReceipt{
		OperationId: "payment-1:hop_1_crypto_send",
		ProviderRef: "provider-1",
		Status:      connectorv1.OperationStatus_OPERATION_WAITING,
	}

	transfer := transferFromReceipt(req, receipt)
	require.NotNil(t, transfer)
	require.Equal(t, "payment-1:hop_1_crypto_send", transfer.GetTransferRef())
	require.Equal(t, "intent-1", transfer.GetIntentRef())
	require.Equal(t, "payment-1:hop_1_crypto_send", transfer.GetOperationRef())
	require.Equal(t, "payment-1", transfer.GetPaymentRef())
	require.Equal(t, chainv1.TransferStatus_TRANSFER_WAITING, transfer.GetStatus())
}

func TestSubmitTransfer_ForwardsOperationAndIntentReferences(t *testing.T) {
	stub := &stubConnectorClient{
		submitResp: &connectorv1.SubmitOperationResponse{
			Receipt: &connectorv1.OperationReceipt{
				OperationId: "payment-1:hop_1_crypto_send",
				Status:      connectorv1.OperationStatus_OPERATION_WAITING,
				ProviderRef: "provider-1",
			},
		},
	}
	client := NewWithClient(Config{}, stub)

	resp, err := client.SubmitTransfer(context.Background(), &chainv1.SubmitTransferRequest{
		IdempotencyKey:  "idem-1",
		OrganizationRef: "org-1",
		SourceWalletRef: "wallet-src",
		Destination: &chainv1.TransferDestination{
			Destination: &chainv1.TransferDestination_ManagedWalletRef{
				ManagedWalletRef: "wallet-dst",
			},
		},
		Amount:       &moneyv1.Money{Amount: "12.34", Currency: "USDT"},
		IntentRef:    "intent-1",
		OperationRef: "payment-1:hop_1_crypto_send",
		PaymentRef:   "payment-1",
	})
	require.NoError(t, err)
	require.NotNil(t, stub.submitReq)
	require.NotNil(t, stub.submitReq.GetOperation())
	require.Equal(t, "intent-1", stub.submitReq.GetOperation().GetIntentRef())
	require.Equal(t, "payment-1:hop_1_crypto_send", stub.submitReq.GetOperation().GetOperationRef())
	require.NotNil(t, resp.GetTransfer())
	require.Equal(t, "payment-1:hop_1_crypto_send", resp.GetTransfer().GetTransferRef())
	require.Equal(t, "payment-1:hop_1_crypto_send", resp.GetTransfer().GetOperationRef())
	require.Equal(t, "intent-1", resp.GetTransfer().GetIntentRef())
}

func TestGetTransfer_UsesTransferRefAsOperationID(t *testing.T) {
	stub := &stubConnectorClient{
		getResp: &connectorv1.GetOperationResponse{
			Operation: &connectorv1.Operation{
				OperationId: "transfer-1",
				Status:      connectorv1.OperationStatus_OPERATION_PROCESSING,
			},
		},
	}
	client := NewWithClient(Config{}, stub)

	resp, err := client.GetTransfer(context.Background(), &chainv1.GetTransferRequest{TransferRef: "transfer-1"})
	require.NoError(t, err)
	require.NotNil(t, stub.getReq)
	require.Equal(t, "transfer-1", stub.getReq.GetOperationId())
	require.NotNil(t, resp.GetTransfer())
	require.Equal(t, "transfer-1", resp.GetTransfer().GetTransferRef())
}
