package quote_persistence_service

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/tech/sendico/payments/storage/model"
	quotestorage "github.com/tech/sendico/payments/storage/quote"
	"github.com/tech/sendico/pkg/merrors"
	quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
	"go.mongodb.org/mongo-driver/v2/bson"
)

func TestPersistSingle(t *testing.T) {
	svc := New()
	store := &fakeQuotesStore{}
	orgID := bson.NewObjectID()

	record, err := svc.Persist(context.Background(), store, PersistInput{
		OrganizationID: orgID,
		QuoteRef:       "quote-1",
		IdempotencyKey: "idem-1",
		Hash:           "hash-1",
		ExpiresAt:      time.Now().Add(time.Minute),
		RequestShape:   model.QuoteRequestShapeSingle,
		Items: []PersistItemInput{
			{
				Intent: &model.PaymentIntent{Ref: "intent-1"},
				Quote: &model.PaymentQuoteSnapshot{
					QuoteRef: "quote-1",
				},
				Status: &StatusInput{
					State: quotationv2.QuoteState_QUOTE_STATE_EXECUTABLE,
				},
			},
		},
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if record == nil {
		t.Fatalf("expected record")
	}
	if store.created == nil {
		t.Fatalf("expected record to be created")
	}
	if store.created.RequestShape != model.QuoteRequestShapeSingle {
		t.Fatalf("unexpected request shape: %q", store.created.RequestShape)
	}
	if len(store.created.Items) != 1 || store.created.Items[0] == nil {
		t.Fatalf("expected single persisted item")
	}
	if store.created.Items[0].Status == nil {
		t.Fatalf("expected item status metadata")
	}
	if store.created.Items[0].Status.State != model.QuoteStateExecutable {
		t.Fatalf("unexpected state: %q", store.created.Items[0].Status.State)
	}
	if store.created.Items[0].Status.BlockReason != model.QuoteBlockReasonUnspecified {
		t.Fatalf("unexpected block_reason: %q", store.created.Items[0].Status.BlockReason)
	}
}

func TestPersistBatch(t *testing.T) {
	svc := New()
	store := &fakeQuotesStore{}
	orgID := bson.NewObjectID()

	record, err := svc.Persist(context.Background(), store, PersistInput{
		OrganizationID: orgID,
		QuoteRef:       "quote-batch-1",
		IdempotencyKey: "idem-batch-1",
		Hash:           "hash-batch-1",
		ExpiresAt:      time.Now().Add(time.Minute),
		RequestShape:   model.QuoteRequestShapeBatch,
		Items: []PersistItemInput{
			{
				Intent: &model.PaymentIntent{Ref: "i1"},
				Quote:  &model.PaymentQuoteSnapshot{QuoteRef: "q1"},
				Status: &StatusInput{
					State:       quotationv2.QuoteState_QUOTE_STATE_BLOCKED,
					BlockReason: quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_ROUTE_UNAVAILABLE,
				},
			},
			{
				Intent: &model.PaymentIntent{Ref: "i2"},
				Quote:  &model.PaymentQuoteSnapshot{QuoteRef: "q2"},
				Status: &StatusInput{
					State: quotationv2.QuoteState_QUOTE_STATE_INDICATIVE,
				},
			},
		},
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if record == nil {
		t.Fatalf("expected record")
	}
	if record.RequestShape != model.QuoteRequestShapeBatch {
		t.Fatalf("unexpected request shape: %q", record.RequestShape)
	}
	if len(record.Items) != 2 {
		t.Fatalf("expected 2 items, got %d", len(record.Items))
	}
	if record.Items[0].Status == nil || record.Items[0].Status.BlockReason != model.QuoteBlockReasonRouteUnavailable {
		t.Fatalf("unexpected first status block reason")
	}
}

func TestPersistValidation(t *testing.T) {
	svc := New()
	store := &fakeQuotesStore{}
	orgID := bson.NewObjectID()

	_, err := svc.Persist(context.Background(), nil, PersistInput{})
	if !errors.Is(err, merrors.ErrInvalidArg) {
		t.Fatalf("expected invalid argument for nil store, got %v", err)
	}

	_, err = svc.Persist(context.Background(), store, PersistInput{
		OrganizationID: orgID,
		QuoteRef:       "q",
		IdempotencyKey: "i",
		Hash:           "h",
		ExpiresAt:      time.Now().Add(time.Minute),
		RequestShape:   model.QuoteRequestShapeSingle,
		Items: []PersistItemInput{
			{
				Intent: &model.PaymentIntent{Ref: "intent"},
				Quote:  &model.PaymentQuoteSnapshot{QuoteRef: "q"},
				Status: &StatusInput{
					State:       quotationv2.QuoteState_QUOTE_STATE_BLOCKED,
					BlockReason: quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED,
				},
			},
		},
	})
	if !errors.Is(err, merrors.ErrInvalidArg) {
		t.Fatalf("expected invalid argument for blocked without reason, got %v", err)
	}

	_, err = svc.Persist(context.Background(), store, PersistInput{
		OrganizationID: orgID,
		QuoteRef:       "q",
		IdempotencyKey: "i",
		Hash:           "h",
		ExpiresAt:      time.Now().Add(time.Minute),
		RequestShape:   model.QuoteRequestShapeSingle,
		Items: []PersistItemInput{
			{
				Intent: &model.PaymentIntent{Ref: "i1"},
				Quote:  &model.PaymentQuoteSnapshot{QuoteRef: "q1"},
				Status: &StatusInput{State: quotationv2.QuoteState_QUOTE_STATE_EXECUTABLE},
			},
			{
				Intent: &model.PaymentIntent{Ref: "i2"},
				Quote:  &model.PaymentQuoteSnapshot{QuoteRef: "q2"},
				Status: &StatusInput{State: quotationv2.QuoteState_QUOTE_STATE_EXECUTABLE},
			},
		},
	})
	if !errors.Is(err, merrors.ErrInvalidArg) {
		t.Fatalf("expected invalid argument for single shape with multiple items, got %v", err)
	}
}

type fakeQuotesStore struct {
	created   *model.PaymentQuoteRecord
	createErr error
}

func (f *fakeQuotesStore) Create(_ context.Context, quote *model.PaymentQuoteRecord) error {
	if f.createErr != nil {
		return f.createErr
	}
	f.created = quote
	return nil
}

func (f *fakeQuotesStore) GetByRef(context.Context, bson.ObjectID, string) (*model.PaymentQuoteRecord, error) {
	return nil, quotestorage.ErrQuoteNotFound
}

func (f *fakeQuotesStore) GetByIdempotencyKey(context.Context, bson.ObjectID, string) (*model.PaymentQuoteRecord, error) {
	return nil, quotestorage.ErrQuoteNotFound
}
