package qsnap

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

	"github.com/tech/sendico/payments/storage/model"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
)

func TestResolveAll_BatchReturnsAllItems(t *testing.T) {
	now := time.Date(2026, time.January, 2, 3, 4, 5, 0, time.UTC)
	orgID := bson.NewObjectID()

	record := &model.PaymentQuoteRecord{
		QuoteRef:     "batch-quote-ref",
		RequestShape: model.QuoteRequestShapeBatch,
		ExpiresAt:    now.Add(time.Minute),
		Items: []*model.PaymentQuoteItemV2{
			{
				Intent: &model.PaymentIntent{Ref: "intent-a", Kind: model.PaymentKindPayout, Amount: &paymenttypes.Money{Amount: "100", Currency: "USDT"}},
				Quote:  &model.PaymentQuoteSnapshot{QuoteRef: "batch-quote-ref", DebitAmount: &paymenttypes.Money{Amount: "100", Currency: "USDT"}},
				Status: &model.QuoteStatusV2{State: model.QuoteStateExecutable},
			},
			{
				Intent: &model.PaymentIntent{Ref: "intent-b", Kind: model.PaymentKindPayout, Amount: &paymenttypes.Money{Amount: "200", Currency: "USDT"}},
				Quote:  &model.PaymentQuoteSnapshot{QuoteRef: "batch-quote-ref", DebitAmount: &paymenttypes.Money{Amount: "200", Currency: "USDT"}},
				Status: &model.QuoteStatusV2{State: model.QuoteStateExecutable},
			},
		},
	}

	resolver := New(Dependencies{Logger: zap.NewNop(), Now: func() time.Time { return now }})

	out, err := resolver.ResolveAll(context.Background(), &fakeStore{
		getByRefFn: func(context.Context, bson.ObjectID, string) (*model.PaymentQuoteRecord, error) {
			return record, nil
		},
	}, ResolveAllInput{
		OrganizationRef: orgID,
		QuotationRef:    "batch-quote-ref",
	})
	if err != nil {
		t.Fatalf("ResolveAll returned error: %v", err)
	}
	if out == nil {
		t.Fatal("expected output")
	}
	if got, want := out.QuotationRef, "batch-quote-ref"; got != want {
		t.Fatalf("quotation_ref mismatch: got=%q want=%q", got, want)
	}
	if got, want := len(out.Items), 2; got != want {
		t.Fatalf("items count mismatch: got=%d want=%d", got, want)
	}
	if got, want := out.Items[0].IntentRef, "intent-a"; got != want {
		t.Fatalf("items[0].intent_ref mismatch: got=%q want=%q", got, want)
	}
	if got, want := out.Items[1].IntentRef, "intent-b"; got != want {
		t.Fatalf("items[1].intent_ref mismatch: got=%q want=%q", got, want)
	}
	if got, want := out.Items[0].QuoteSnapshot.DebitAmount.Amount, "100"; got != want {
		t.Fatalf("items[0].quote debit amount mismatch: got=%q want=%q", got, want)
	}
	if got, want := out.Items[1].QuoteSnapshot.DebitAmount.Amount, "200"; got != want {
		t.Fatalf("items[1].quote debit amount mismatch: got=%q want=%q", got, want)
	}
}

func TestResolveAll_SingleShapeReturnsOneItem(t *testing.T) {
	now := time.Date(2026, time.January, 2, 3, 4, 5, 0, time.UTC)
	orgID := bson.NewObjectID()

	record := &model.PaymentQuoteRecord{
		QuoteRef:     "single-quote-ref",
		RequestShape: model.QuoteRequestShapeSingle,
		ExpiresAt:    now.Add(time.Minute),
		Items: []*model.PaymentQuoteItemV2{
			{
				Intent: &model.PaymentIntent{Ref: "intent-1", Kind: model.PaymentKindPayout, Amount: &paymenttypes.Money{Amount: "50", Currency: "USD"}},
				Quote:  &model.PaymentQuoteSnapshot{QuoteRef: "single-quote-ref"},
				Status: &model.QuoteStatusV2{State: model.QuoteStateExecutable},
			},
		},
	}

	resolver := New(Dependencies{Logger: zap.NewNop(), Now: func() time.Time { return now }})

	out, err := resolver.ResolveAll(context.Background(), &fakeStore{
		getByRefFn: func(context.Context, bson.ObjectID, string) (*model.PaymentQuoteRecord, error) {
			return record, nil
		},
	}, ResolveAllInput{
		OrganizationRef: orgID,
		QuotationRef:    "single-quote-ref",
	})
	if err != nil {
		t.Fatalf("ResolveAll returned error: %v", err)
	}
	if got, want := len(out.Items), 1; got != want {
		t.Fatalf("items count mismatch: got=%d want=%d", got, want)
	}
	if got, want := out.Items[0].IntentRef, "intent-1"; got != want {
		t.Fatalf("items[0].intent_ref mismatch: got=%q want=%q", got, want)
	}
}

func TestResolveAll_NonExecutableItemFails(t *testing.T) {
	now := time.Date(2026, time.January, 2, 3, 4, 5, 0, time.UTC)
	orgID := bson.NewObjectID()

	record := &model.PaymentQuoteRecord{
		QuoteRef:     "batch-mixed",
		RequestShape: model.QuoteRequestShapeBatch,
		ExpiresAt:    now.Add(time.Minute),
		Items: []*model.PaymentQuoteItemV2{
			{
				Intent: &model.PaymentIntent{Ref: "intent-ok", Kind: model.PaymentKindPayout},
				Quote:  &model.PaymentQuoteSnapshot{},
				Status: &model.QuoteStatusV2{State: model.QuoteStateExecutable},
			},
			{
				Intent: &model.PaymentIntent{Ref: "intent-blocked", Kind: model.PaymentKindPayout},
				Quote:  &model.PaymentQuoteSnapshot{},
				Status: &model.QuoteStatusV2{State: model.QuoteStateBlocked, BlockReason: model.QuoteBlockReasonInsufficientLiquidity},
			},
		},
	}

	resolver := New(Dependencies{Logger: zap.NewNop(), Now: func() time.Time { return now }})

	_, err := resolver.ResolveAll(context.Background(), &fakeStore{
		getByRefFn: func(context.Context, bson.ObjectID, string) (*model.PaymentQuoteRecord, error) {
			return record, nil
		},
	}, ResolveAllInput{
		OrganizationRef: orgID,
		QuotationRef:    "batch-mixed",
	})
	if err == nil {
		t.Fatal("expected error for non-executable item")
	}
	if !errors.Is(err, ErrQuoteNotExecutable) {
		t.Fatalf("expected ErrQuoteNotExecutable, got %v", err)
	}
}

func TestResolveAll_ExpiredQuoteFails(t *testing.T) {
	now := time.Date(2026, time.January, 2, 3, 4, 5, 0, time.UTC)
	orgID := bson.NewObjectID()

	record := &model.PaymentQuoteRecord{
		QuoteRef:     "expired-quote",
		RequestShape: model.QuoteRequestShapeBatch,
		ExpiresAt:    now.Add(-time.Minute),
		Items: []*model.PaymentQuoteItemV2{
			{
				Intent: &model.PaymentIntent{Ref: "intent-1", Kind: model.PaymentKindPayout},
				Quote:  &model.PaymentQuoteSnapshot{},
				Status: &model.QuoteStatusV2{State: model.QuoteStateExecutable},
			},
		},
	}

	resolver := New(Dependencies{Logger: zap.NewNop(), Now: func() time.Time { return now }})

	_, err := resolver.ResolveAll(context.Background(), &fakeStore{
		getByRefFn: func(context.Context, bson.ObjectID, string) (*model.PaymentQuoteRecord, error) {
			return record, nil
		},
	}, ResolveAllInput{
		OrganizationRef: orgID,
		QuotationRef:    "expired-quote",
	})
	if err == nil {
		t.Fatal("expected error for expired quote")
	}
	if !errors.Is(err, ErrQuoteExpired) {
		t.Fatalf("expected ErrQuoteExpired, got %v", err)
	}
}

func TestResolveAll_EmptyQuotationRefFails(t *testing.T) {
	resolver := New(Dependencies{Logger: zap.NewNop()})

	_, err := resolver.ResolveAll(context.Background(), &fakeStore{}, ResolveAllInput{
		OrganizationRef: bson.NewObjectID(),
		QuotationRef:    "",
	})
	if err == nil {
		t.Fatal("expected error for empty quotation_ref")
	}
}

func TestResolveAll_QuoteNotFoundFails(t *testing.T) {
	resolver := New(Dependencies{Logger: zap.NewNop()})

	_, err := resolver.ResolveAll(context.Background(), &fakeStore{}, ResolveAllInput{
		OrganizationRef: bson.NewObjectID(),
		QuotationRef:    "nonexistent",
	})
	if err == nil {
		t.Fatal("expected error for not-found quote")
	}
	if !errors.Is(err, ErrQuoteNotFound) {
		t.Fatalf("expected ErrQuoteNotFound, got %v", err)
	}
}

func TestResolveAll_SetsQuoteRefWhenEmpty(t *testing.T) {
	now := time.Date(2026, time.January, 2, 3, 4, 5, 0, time.UTC)
	orgID := bson.NewObjectID()

	record := &model.PaymentQuoteRecord{
		QuoteRef:     "batch-ref",
		RequestShape: model.QuoteRequestShapeBatch,
		ExpiresAt:    now.Add(time.Minute),
		Items: []*model.PaymentQuoteItemV2{
			{
				Intent: &model.PaymentIntent{Ref: "intent-1", Kind: model.PaymentKindPayout},
				Quote:  &model.PaymentQuoteSnapshot{QuoteRef: ""},
				Status: &model.QuoteStatusV2{State: model.QuoteStateExecutable},
			},
		},
	}

	resolver := New(Dependencies{Logger: zap.NewNop(), Now: func() time.Time { return now }})

	out, err := resolver.ResolveAll(context.Background(), &fakeStore{
		getByRefFn: func(context.Context, bson.ObjectID, string) (*model.PaymentQuoteRecord, error) {
			return record, nil
		},
	}, ResolveAllInput{
		OrganizationRef: orgID,
		QuotationRef:    "batch-ref",
	})
	if err != nil {
		t.Fatalf("ResolveAll returned error: %v", err)
	}
	if got, want := out.Items[0].QuoteSnapshot.QuoteRef, "batch-ref"; got != want {
		t.Fatalf("quote_ref should be back-filled: got=%q want=%q", got, want)
	}
}
