package quote_request_validator_v2

import (
	"errors"
	"strings"
	"testing"

	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	paymentv1 "github.com/tech/sendico/pkg/proto/common/payment/v1"
	endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
	quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
	sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1"
	"go.mongodb.org/mongo-driver/v2/bson"
)

func TestValidateQuotePayment_Success(t *testing.T) {
	validator := New()
	orgID := bson.NewObjectID()
	req := &quotationv2.QuotePaymentRequest{
		Meta:           &sharedv1.RequestMeta{OrganizationRef: orgID.Hex()},
		IdempotencyKey: "idem-1",
		Intent:         validQuoteIntent(),
		PreviewOnly:    false,
		InitiatorRef:   "actor-1",
	}

	ctx, err := validator.ValidateQuotePayment(req)
	if err != nil {
		t.Fatalf("ValidateQuotePayment returned error: %v", err)
	}
	if ctx == nil {
		t.Fatalf("expected validation context")
	}
	if got, want := ctx.OrganizationRef, orgID.Hex(); got != want {
		t.Fatalf("expected organization_ref %q, got %q", want, got)
	}
	if got, want := ctx.OrganizationID, orgID; got != want {
		t.Fatalf("expected organization_id %s, got %s", want.Hex(), got.Hex())
	}
	if got, want := ctx.IdempotencyKey, "idem-1"; got != want {
		t.Fatalf("expected idempotency_key %q, got %q", want, got)
	}
	if got, want := ctx.InitiatorRef, "actor-1"; got != want {
		t.Fatalf("expected initiator_ref %q, got %q", want, got)
	}
	if got, want := ctx.IntentCount, 1; got != want {
		t.Fatalf("expected intent_count %d, got %d", want, got)
	}
}

func TestValidateQuotePayment_Rules(t *testing.T) {
	validator := New()
	orgHex := bson.NewObjectID().Hex()

	testCases := []struct {
		name     string
		req      *quotationv2.QuotePaymentRequest
		checkErr func(error) bool
	}{
		{
			name: "idempotency required for non-preview",
			req: &quotationv2.QuotePaymentRequest{
				Meta:         &sharedv1.RequestMeta{OrganizationRef: orgHex},
				Intent:       validQuoteIntent(),
				PreviewOnly:  false,
				InitiatorRef: "actor-1",
			},
			checkErr: func(err error) bool { return errors.Is(err, ErrIdempotencyRequired) },
		},
		{
			name: "preview must not include idempotency",
			req: &quotationv2.QuotePaymentRequest{
				Meta:           &sharedv1.RequestMeta{OrganizationRef: orgHex},
				IdempotencyKey: "idem-1",
				Intent:         validQuoteIntent(),
				PreviewOnly:    true,
				InitiatorRef:   "actor-1",
			},
			checkErr: func(err error) bool { return errors.Is(err, ErrPreviewWithIdempotency) },
		},
		{
			name: "initiator ref required",
			req: &quotationv2.QuotePaymentRequest{
				Meta:           &sharedv1.RequestMeta{OrganizationRef: orgHex},
				IdempotencyKey: "idem-1",
				Intent:         validQuoteIntent(),
				PreviewOnly:    false,
			},
			checkErr: func(err error) bool { return errors.Is(err, ErrInitiatorRefRequired) },
		},
		{
			name: "invalid org ref",
			req: &quotationv2.QuotePaymentRequest{
				Meta:           &sharedv1.RequestMeta{OrganizationRef: "bad-org"},
				IdempotencyKey: "idem-1",
				Intent:         validQuoteIntent(),
				PreviewOnly:    false,
				InitiatorRef:   "actor-1",
			},
			checkErr: func(err error) bool {
				return err != nil && strings.Contains(err.Error(), "organization_ref must be a valid objectID")
			},
		},
		{
			name: "source required",
			req: &quotationv2.QuotePaymentRequest{
				Meta:           &sharedv1.RequestMeta{OrganizationRef: orgHex},
				IdempotencyKey: "idem-1",
				Intent: &quotationv2.QuoteIntent{
					Destination: endpointWithMethodRef("pm-dst"),
					Amount:      &moneyv1.Money{Amount: "10", Currency: "USD"},
				},
				PreviewOnly:  false,
				InitiatorRef: "actor-1",
			},
			checkErr: func(err error) bool { return err != nil && strings.Contains(err.Error(), "intent.source is required") },
		},
		{
			name: "invalid settlement mode",
			req: &quotationv2.QuotePaymentRequest{
				Meta:           &sharedv1.RequestMeta{OrganizationRef: orgHex},
				IdempotencyKey: "idem-1",
				Intent: &quotationv2.QuoteIntent{
					Source:         endpointWithMethodRef("pm-src"),
					Destination:    endpointWithMethodRef("pm-dst"),
					Amount:         &moneyv1.Money{Amount: "10", Currency: "USD"},
					FeeTreatment:   quotationv2.FeeTreatment_FEE_TREATMENT_ADD_TO_SOURCE,
					SettlementMode: paymentv1.SettlementMode(99),
				},
				PreviewOnly:  false,
				InitiatorRef: "actor-1",
			},
			checkErr: func(err error) bool {
				return err != nil && strings.Contains(err.Error(), "intent.settlement_mode is invalid")
			},
		},
		{
			name: "invalid fee treatment",
			req: &quotationv2.QuotePaymentRequest{
				Meta:           &sharedv1.RequestMeta{OrganizationRef: orgHex},
				IdempotencyKey: "idem-1",
				Intent: &quotationv2.QuoteIntent{
					Source:         endpointWithMethodRef("pm-src"),
					Destination:    endpointWithMethodRef("pm-dst"),
					Amount:         &moneyv1.Money{Amount: "10", Currency: "USD"},
					SettlementMode: paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE,
					FeeTreatment:   quotationv2.FeeTreatment(99),
				},
				PreviewOnly:  false,
				InitiatorRef: "actor-1",
			},
			checkErr: func(err error) bool {
				return err != nil && strings.Contains(err.Error(), "intent.fee_treatment is invalid")
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			_, err := validator.ValidateQuotePayment(tc.req)
			if !tc.checkErr(err) {
				t.Fatalf("unexpected error: %v", err)
			}
		})
	}
}

func TestValidateQuotePayments_Success(t *testing.T) {
	validator := New()
	orgID := bson.NewObjectID()
	req := &quotationv2.QuotePaymentsRequest{
		Meta:         &sharedv1.RequestMeta{OrganizationRef: orgID.Hex()},
		Intents:      []*quotationv2.QuoteIntent{validQuoteIntent(), validQuoteIntent()},
		PreviewOnly:  true,
		InitiatorRef: "actor-1",
	}

	ctx, err := validator.ValidateQuotePayments(req)
	if err != nil {
		t.Fatalf("ValidateQuotePayments returned error: %v", err)
	}
	if ctx == nil {
		t.Fatalf("expected validation context")
	}
	if got, want := ctx.IntentCount, 2; got != want {
		t.Fatalf("expected intent_count %d, got %d", want, got)
	}
	if got, want := ctx.PreviewOnly, true; got != want {
		t.Fatalf("expected preview_only %v, got %v", want, got)
	}
	if got, want := ctx.IdempotencyKey, ""; got != want {
		t.Fatalf("expected idempotency_key %q, got %q", want, got)
	}
}

func TestValidateQuotePayments_Rules(t *testing.T) {
	validator := New()
	orgHex := bson.NewObjectID().Hex()

	testCases := []struct {
		name     string
		req      *quotationv2.QuotePaymentsRequest
		checkErr func(error) bool
	}{
		{
			name: "intents required",
			req: &quotationv2.QuotePaymentsRequest{
				Meta:           &sharedv1.RequestMeta{OrganizationRef: orgHex},
				IdempotencyKey: "idem-1",
				PreviewOnly:    false,
				InitiatorRef:   "actor-1",
			},
			checkErr: func(err error) bool { return err != nil && strings.Contains(err.Error(), "intents are required") },
		},
		{
			name: "idempotency required for non-preview",
			req: &quotationv2.QuotePaymentsRequest{
				Meta:         &sharedv1.RequestMeta{OrganizationRef: orgHex},
				Intents:      []*quotationv2.QuoteIntent{validQuoteIntent()},
				PreviewOnly:  false,
				InitiatorRef: "actor-1",
			},
			checkErr: func(err error) bool { return errors.Is(err, ErrIdempotencyRequired) },
		},
		{
			name: "preview must not include idempotency",
			req: &quotationv2.QuotePaymentsRequest{
				Meta:           &sharedv1.RequestMeta{OrganizationRef: orgHex},
				IdempotencyKey: "idem-1",
				Intents:        []*quotationv2.QuoteIntent{validQuoteIntent()},
				PreviewOnly:    true,
				InitiatorRef:   "actor-1",
			},
			checkErr: func(err error) bool { return errors.Is(err, ErrPreviewWithIdempotency) },
		},
		{
			name: "initiator ref required",
			req: &quotationv2.QuotePaymentsRequest{
				Meta:        &sharedv1.RequestMeta{OrganizationRef: orgHex},
				Intents:     []*quotationv2.QuoteIntent{validQuoteIntent()},
				PreviewOnly: true,
			},
			checkErr: func(err error) bool { return errors.Is(err, ErrInitiatorRefRequired) },
		},
		{
			name: "indexed intent validation",
			req: &quotationv2.QuotePaymentsRequest{
				Meta:           &sharedv1.RequestMeta{OrganizationRef: orgHex},
				IdempotencyKey: "idem-1",
				Intents: []*quotationv2.QuoteIntent{
					{
						Source:      endpointWithMethodRef("pm-src"),
						Destination: endpointWithMethodRef("pm-dst"),
					},
				},
				PreviewOnly:  false,
				InitiatorRef: "actor-1",
			},
			checkErr: func(err error) bool {
				return err != nil && strings.Contains(err.Error(), "intents[0].amount is required")
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			_, err := validator.ValidateQuotePayments(tc.req)
			if !tc.checkErr(err) {
				t.Fatalf("unexpected error: %v", err)
			}
		})
	}
}

func TestValidateQuotePayment_EconomicsKnobsAreIndependent(t *testing.T) {
	validator := New()
	orgHex := bson.NewObjectID().Hex()

	combinations := []struct {
		name string
		mode paymentv1.SettlementMode
		fee  quotationv2.FeeTreatment
	}{
		{
			name: "both knobs omitted",
			mode: paymentv1.SettlementMode_SETTLEMENT_UNSPECIFIED,
			fee:  quotationv2.FeeTreatment_FEE_TREATMENT_UNSPECIFIED,
		},
		{
			name: "fix_source with add_to_source",
			mode: paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE,
			fee:  quotationv2.FeeTreatment_FEE_TREATMENT_ADD_TO_SOURCE,
		},
		{
			name: "fix_source with deduct_from_destination",
			mode: paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE,
			fee:  quotationv2.FeeTreatment_FEE_TREATMENT_DEDUCT_FROM_DESTINATION,
		},
		{
			name: "fix_received with add_to_source",
			mode: paymentv1.SettlementMode_SETTLEMENT_FIX_RECEIVED,
			fee:  quotationv2.FeeTreatment_FEE_TREATMENT_ADD_TO_SOURCE,
		},
		{
			name: "fix_received with deduct_from_destination",
			mode: paymentv1.SettlementMode_SETTLEMENT_FIX_RECEIVED,
			fee:  quotationv2.FeeTreatment_FEE_TREATMENT_DEDUCT_FROM_DESTINATION,
		},
		{
			name: "fee specified while settlement mode omitted",
			mode: paymentv1.SettlementMode_SETTLEMENT_UNSPECIFIED,
			fee:  quotationv2.FeeTreatment_FEE_TREATMENT_DEDUCT_FROM_DESTINATION,
		},
	}

	for _, tc := range combinations {
		t.Run(tc.name, func(t *testing.T) {
			req := &quotationv2.QuotePaymentRequest{
				Meta:           &sharedv1.RequestMeta{OrganizationRef: orgHex},
				IdempotencyKey: "idem-1",
				Intent: &quotationv2.QuoteIntent{
					Source:         endpointWithMethodRef("pm-src"),
					Destination:    endpointWithMethodRef("pm-dst"),
					Amount:         &moneyv1.Money{Amount: "10", Currency: "USD"},
					SettlementMode: tc.mode,
					FeeTreatment:   tc.fee,
				},
				InitiatorRef: "actor-1",
			}

			if _, err := validator.ValidateQuotePayment(req); err != nil {
				t.Fatalf("expected economics combination to be accepted, got %v", err)
			}
		})
	}
}

func validQuoteIntent() *quotationv2.QuoteIntent {
	return &quotationv2.QuoteIntent{
		Source:         endpointWithMethodRef("pm-src"),
		Destination:    endpointWithMethodRef("pm-dst"),
		Amount:         &moneyv1.Money{Amount: "10", Currency: "USD"},
		SettlementMode: paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE,
	}
}

func endpointWithMethodRef(methodRef string) *endpointv1.PaymentEndpoint {
	return &endpointv1.PaymentEndpoint{
		Source: &endpointv1.PaymentEndpoint_PaymentMethodRef{
			PaymentMethodRef: methodRef,
		},
	}
}
