package serverimp

import (
	"context"

	oracleclient "github.com/tech/sendico/fx/oracle/client"
	chainclient "github.com/tech/sendico/gateway/chain/client"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/payments/rail"
	feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
	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"
	ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
	"google.golang.org/grpc"
)

type discoveryFeeClient struct {
	resolver *discoveryClientResolver
}

func (c *discoveryFeeClient) Available() bool {
	if c == nil || c.resolver == nil {
		return false
	}
	return c.resolver.FeesAvailable()
}

func (c *discoveryFeeClient) QuoteFees(ctx context.Context, req *feesv1.QuoteFeesRequest, opts ...grpc.CallOption) (*feesv1.QuoteFeesResponse, error) {
	client, err := c.resolver.FeesClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.QuoteFees(ctx, req, opts...)
}

func (c *discoveryFeeClient) PrecomputeFees(ctx context.Context, req *feesv1.PrecomputeFeesRequest, opts ...grpc.CallOption) (*feesv1.PrecomputeFeesResponse, error) {
	client, err := c.resolver.FeesClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.PrecomputeFees(ctx, req, opts...)
}

func (c *discoveryFeeClient) ValidateFeeToken(ctx context.Context, req *feesv1.ValidateFeeTokenRequest, opts ...grpc.CallOption) (*feesv1.ValidateFeeTokenResponse, error) {
	client, err := c.resolver.FeesClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.ValidateFeeToken(ctx, req, opts...)
}

type discoveryLedgerClient struct {
	resolver *discoveryClientResolver
}

func (c *discoveryLedgerClient) Available() bool {
	if c == nil || c.resolver == nil {
		return false
	}
	return c.resolver.LedgerAvailable()
}

func (c *discoveryLedgerClient) ReadBalance(ctx context.Context, accountID string) (*moneyv1.Money, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.ReadBalance(ctx, accountID)
}

func (c *discoveryLedgerClient) CreateTransaction(ctx context.Context, tx rail.LedgerTx) (string, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return "", err
	}
	return client.CreateTransaction(ctx, tx)
}

func (c *discoveryLedgerClient) TransferInternal(ctx context.Context, req *ledgerv1.TransferRequest) (*ledgerv1.PostResponse, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.TransferInternal(ctx, req)
}

func (c *discoveryLedgerClient) HoldBalance(ctx context.Context, accountID string, amount string) error {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return err
	}
	return client.HoldBalance(ctx, accountID, amount)
}

func (c *discoveryLedgerClient) CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.CreateAccount(ctx, req)
}

func (c *discoveryLedgerClient) ListAccounts(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.ListAccounts(ctx, req)
}

func (c *discoveryLedgerClient) ListConnectorAccounts(ctx context.Context, req *connectorv1.ListAccountsRequest) (*connectorv1.ListAccountsResponse, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.ListConnectorAccounts(ctx, req)
}

func (c *discoveryLedgerClient) PostCreditWithCharges(ctx context.Context, req *ledgerv1.PostCreditRequest) (*ledgerv1.PostResponse, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.PostCreditWithCharges(ctx, req)
}

func (c *discoveryLedgerClient) PostDebitWithCharges(ctx context.Context, req *ledgerv1.PostDebitRequest) (*ledgerv1.PostResponse, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.PostDebitWithCharges(ctx, req)
}

func (c *discoveryLedgerClient) PostExternalCreditWithCharges(ctx context.Context, req *ledgerv1.PostCreditRequest) (*ledgerv1.PostResponse, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.PostExternalCreditWithCharges(ctx, req)
}

func (c *discoveryLedgerClient) PostExternalDebitWithCharges(ctx context.Context, req *ledgerv1.PostDebitRequest) (*ledgerv1.PostResponse, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.PostExternalDebitWithCharges(ctx, req)
}

func (c *discoveryLedgerClient) ApplyFXWithCharges(ctx context.Context, req *ledgerv1.FXRequest) (*ledgerv1.PostResponse, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.ApplyFXWithCharges(ctx, req)
}

func (c *discoveryLedgerClient) GetBalance(ctx context.Context, req *ledgerv1.GetBalanceRequest) (*ledgerv1.BalanceResponse, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.GetBalance(ctx, req)
}

func (c *discoveryLedgerClient) GetJournalEntry(ctx context.Context, req *ledgerv1.GetEntryRequest) (*ledgerv1.JournalEntryResponse, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.GetJournalEntry(ctx, req)
}

func (c *discoveryLedgerClient) GetStatement(ctx context.Context, req *ledgerv1.GetStatementRequest) (*ledgerv1.StatementResponse, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.GetStatement(ctx, req)
}

func (c *discoveryLedgerClient) BlockAccount(ctx context.Context, req *ledgerv1.BlockAccountRequest) (*ledgerv1.BlockAccountResponse, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.BlockAccount(ctx, req)
}

func (c *discoveryLedgerClient) UnblockAccount(ctx context.Context, req *ledgerv1.UnblockAccountRequest) (*ledgerv1.UnblockAccountResponse, error) {
	client, err := c.resolver.LedgerClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.UnblockAccount(ctx, req)
}

func (c *discoveryLedgerClient) Close() error {
	if c == nil || c.resolver == nil {
		return nil
	}
	client, err := c.resolver.LedgerClient(context.Background())
	if err != nil {
		return err
	}
	return client.Close()
}

type discoveryOracleClient struct {
	resolver *discoveryClientResolver
}

func (c *discoveryOracleClient) Available() bool {
	if c == nil || c.resolver == nil {
		return false
	}
	return c.resolver.OracleAvailable()
}

func (c *discoveryOracleClient) LatestRate(ctx context.Context, req oracleclient.LatestRateParams) (*oracleclient.RateSnapshot, error) {
	client, err := c.resolver.OracleClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.LatestRate(ctx, req)
}

func (c *discoveryOracleClient) GetQuote(ctx context.Context, req oracleclient.GetQuoteParams) (*oracleclient.Quote, error) {
	client, err := c.resolver.OracleClient(ctx)
	if err != nil {
		return nil, err
	}
	return client.GetQuote(ctx, req)
}

func (c *discoveryOracleClient) Close() error {
	if c == nil || c.resolver == nil {
		return nil
	}
	client, err := c.resolver.OracleClient(context.Background())
	if err != nil {
		return err
	}
	return client.Close()
}

type discoveryChainClient struct {
	resolver  *discoveryClientResolver
	invokeURI string
}

func (c *discoveryChainClient) Available() bool {
	return c != nil && c.resolver != nil && c.invokeURI != ""
}

func (c *discoveryChainClient) client(ctx context.Context) (chainclient.Client, error) {
	if c == nil || c.resolver == nil {
		return nil, merrors.Internal("discovery chain client unavailable")
	}
	return c.resolver.ChainClient(ctx, c.invokeURI)
}

func (c *discoveryChainClient) CreateManagedWallet(ctx context.Context, req *chainv1.CreateManagedWalletRequest) (*chainv1.CreateManagedWalletResponse, error) {
	client, err := c.client(ctx)
	if err != nil {
		return nil, err
	}
	return client.CreateManagedWallet(ctx, req)
}

func (c *discoveryChainClient) GetManagedWallet(ctx context.Context, req *chainv1.GetManagedWalletRequest) (*chainv1.GetManagedWalletResponse, error) {
	client, err := c.client(ctx)
	if err != nil {
		return nil, err
	}
	return client.GetManagedWallet(ctx, req)
}

func (c *discoveryChainClient) ListManagedWallets(ctx context.Context, req *chainv1.ListManagedWalletsRequest) (*chainv1.ListManagedWalletsResponse, error) {
	client, err := c.client(ctx)
	if err != nil {
		return nil, err
	}
	return client.ListManagedWallets(ctx, req)
}

func (c *discoveryChainClient) GetWalletBalance(ctx context.Context, req *chainv1.GetWalletBalanceRequest) (*chainv1.GetWalletBalanceResponse, error) {
	client, err := c.client(ctx)
	if err != nil {
		return nil, err
	}
	return client.GetWalletBalance(ctx, req)
}

func (c *discoveryChainClient) SubmitTransfer(ctx context.Context, req *chainv1.SubmitTransferRequest) (*chainv1.SubmitTransferResponse, error) {
	client, err := c.client(ctx)
	if err != nil {
		return nil, err
	}
	return client.SubmitTransfer(ctx, req)
}

func (c *discoveryChainClient) GetTransfer(ctx context.Context, req *chainv1.GetTransferRequest) (*chainv1.GetTransferResponse, error) {
	client, err := c.client(ctx)
	if err != nil {
		return nil, err
	}
	return client.GetTransfer(ctx, req)
}

func (c *discoveryChainClient) ListTransfers(ctx context.Context, req *chainv1.ListTransfersRequest) (*chainv1.ListTransfersResponse, error) {
	client, err := c.client(ctx)
	if err != nil {
		return nil, err
	}
	return client.ListTransfers(ctx, req)
}

func (c *discoveryChainClient) EstimateTransferFee(ctx context.Context, req *chainv1.EstimateTransferFeeRequest) (*chainv1.EstimateTransferFeeResponse, error) {
	client, err := c.client(ctx)
	if err != nil {
		return nil, err
	}
	return client.EstimateTransferFee(ctx, req)
}

func (c *discoveryChainClient) ComputeGasTopUp(ctx context.Context, req *chainv1.ComputeGasTopUpRequest) (*chainv1.ComputeGasTopUpResponse, error) {
	client, err := c.client(ctx)
	if err != nil {
		return nil, err
	}
	return client.ComputeGasTopUp(ctx, req)
}

func (c *discoveryChainClient) EnsureGasTopUp(ctx context.Context, req *chainv1.EnsureGasTopUpRequest) (*chainv1.EnsureGasTopUpResponse, error) {
	client, err := c.client(ctx)
	if err != nil {
		return nil, err
	}
	return client.EnsureGasTopUp(ctx, req)
}

func (c *discoveryChainClient) Close() error {
	if c == nil || c.resolver == nil {
		return nil
	}
	client, err := c.resolver.ChainClient(context.Background(), c.invokeURI)
	if err != nil {
		return err
	}
	return client.Close()
}
