package client

import (
	"context"
	"testing"
	"time"

	fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/types/known/timestamppb"
)

type stubOracle struct {
	latestResp *oraclev1.LatestRateResponse
	latestErr  error

	quoteResp *oraclev1.GetQuoteResponse
	quoteErr  error

	lastLatest *oraclev1.LatestRateRequest
	lastQuote  *oraclev1.GetQuoteRequest
}

func (s *stubOracle) LatestRate(ctx context.Context, in *oraclev1.LatestRateRequest, _ ...grpc.CallOption) (*oraclev1.LatestRateResponse, error) {
	s.lastLatest = in
	return s.latestResp, s.latestErr
}

func (s *stubOracle) GetQuote(ctx context.Context, in *oraclev1.GetQuoteRequest, _ ...grpc.CallOption) (*oraclev1.GetQuoteResponse, error) {
	s.lastQuote = in
	return s.quoteResp, s.quoteErr
}

func TestLatestRate(t *testing.T) {
	expectedTime := time.Date(2024, 1, 1, 15, 0, 0, 0, time.UTC)
	stub := &stubOracle{
		latestResp: &oraclev1.LatestRateResponse{
			Rate: &oraclev1.RateSnapshot{
				Pair:       &fxv1.CurrencyPair{Base: "USD", Quote: "EUR"},
				Mid:        &moneyv1.Decimal{Value: "1.1000"},
				Bid:        &moneyv1.Decimal{Value: "1.0995"},
				Ask:        &moneyv1.Decimal{Value: "1.1005"},
				SpreadBps:  &moneyv1.Decimal{Value: "5"},
				Provider:   "ECB",
				RateRef:    "ECB-20240101",
				AsofUnixMs: expectedTime.UnixMilli(),
			},
		},
	}

	client := NewWithClient(Config{}, stub)
	resp, err := client.LatestRate(context.Background(), LatestRateParams{
		Meta: RequestMeta{
			TenantRef:       "tenant",
			OrganizationRef: "org",
		},
		Pair:     &fxv1.CurrencyPair{Base: "USD", Quote: "EUR"},
		Provider: "ECB",
	})
	if err != nil {
		t.Fatalf("LatestRate returned error: %v", err)
	}

	if stub.lastLatest.GetProvider() != "ECB" {
		t.Fatalf("expected provider to propagate, got %s", stub.lastLatest.GetProvider())
	}
	if resp.Provider != "ECB" || resp.RateRef != "ECB-20240101" {
		t.Fatalf("unexpected response: %+v", resp)
	}
	if !resp.AsOf.Equal(expectedTime) {
		t.Fatalf("expected as-of %s, got %s", expectedTime, resp.AsOf)
	}
}

func TestGetQuote(t *testing.T) {
	expiresAt := time.Date(2024, 2, 2, 12, 0, 0, 0, time.UTC)
	pricedAt := time.Date(2024, 2, 2, 11, 59, 0, 0, time.UTC)
	stub := &stubOracle{
		quoteResp: &oraclev1.GetQuoteResponse{
			Quote: &oraclev1.Quote{
				QuoteRef:        "quote-123",
				Pair:            &fxv1.CurrencyPair{Base: "GBP", Quote: "USD"},
				Side:            fxv1.Side_BUY_BASE_SELL_QUOTE,
				Price:           &moneyv1.Decimal{Value: "1.2500"},
				BaseAmount:      &moneyv1.Money{Amount: "100.00", Currency: "GBP"},
				QuoteAmount:     &moneyv1.Money{Amount: "125.00", Currency: "USD"},
				ExpiresAtUnixMs: expiresAt.UnixMilli(),
				PricedAt:        timestamppb.New(pricedAt),
				Provider:        "Test",
				RateRef:         "test-ref",
				Firm:            true,
			},
		},
	}

	client := NewWithClient(Config{}, stub)
	resp, err := client.GetQuote(context.Background(), GetQuoteParams{
		Pair:       &fxv1.CurrencyPair{Base: "GBP", Quote: "USD"},
		Side:       fxv1.Side_BUY_BASE_SELL_QUOTE,
		BaseAmount: &moneyv1.Money{Amount: "100.00", Currency: "GBP"},
		Firm:       true,
		TTL:        2 * time.Second,
	})
	if err != nil {
		t.Fatalf("GetQuote returned error: %v", err)
	}

	if stub.lastQuote.GetFirm() != true {
		t.Fatalf("expected firm flag to propagate")
	}
	if stub.lastQuote.GetTtlMs() == 0 {
		t.Fatalf("expected ttl to be populated")
	}
	if resp.QuoteRef != "quote-123" || resp.Price != "1.2500" || !resp.ExpiresAt.Equal(expiresAt) {
		t.Fatalf("unexpected quote response: %+v", resp)
	}
	if !resp.PricedAt.Equal(pricedAt) {
		t.Fatalf("expected priced_at %s, got %s", pricedAt, resp.PricedAt)
	}
}
