package booking

import (
	"context"
	"errors"
	"log/slog"
	"testing"
	"time"

	"gorm.io/gorm"

	"github.com/rjNemo/rentease/internal/config"
	stripeclient "github.com/rjNemo/rentease/internal/driver/stripe"
)

type fakeStripeClient struct {
	payments []stripeclient.Payment
	err      error
}

func (f *fakeStripeClient) ListPayments(ctx context.Context, params stripeclient.ListPaymentsParams) ([]stripeclient.Payment, error) {
	return f.payments, f.err
}

func (f *fakeStripeClient) CreatePaymentLink(ctx context.Context, params stripeclient.CreatePaymentLinkParams) (string, error) {
	return "", nil
}

type mockStore struct {
	upserts    []*Payment
	err        error
	byStripeID map[string]*Payment
}

func (m *mockStore) record(p *Payment) (*Payment, error) {
	cp := *p
	m.upserts = append(m.upserts, &cp)
	if cp.StripePaymentID != nil {
		if m.byStripeID == nil {
			m.byStripeID = make(map[string]*Payment)
		}
		clone := cp
		m.byStripeID[*cp.StripePaymentID] = &clone
	}
	if m.err != nil {
		return nil, m.err
	}
	return &cp, nil
}

func (m *mockStore) All() []*Line                                    { return nil }
func (m *mockStore) Search(string) []*Line                           { return nil }
func (m *mockStore) List(time.Time, time.Time) ([]*Line, error)      { return nil, nil }
func (m *mockStore) CardTotal(time.Time, time.Time) (float64, error) { return 0, nil }
func (m *mockStore) Get(int) *Booking                                { return nil }
func (m *mockStore) Create(*Booking) error                           { return nil }
func (m *mockStore) Update(*Booking) error                           { return nil }
func (m *mockStore) Cancel(int) error                                { return nil }
func (m *mockStore) CreateItem(*Item) error                          { return nil }
func (m *mockStore) PayItem(int) (*Item, error)                      { return nil, nil }
func (m *mockStore) GetItem(int) (*Item, error)                      { return nil, nil }
func (m *mockStore) UpdateItem(int, string, string, string, int, float64) (*Item, error) {
	return nil, nil
}
func (m *mockStore) CreatePayment(*Payment) (*Payment, error)             { return nil, nil }
func (m *mockStore) GetPayment(int) (*Payment, error)                     { return nil, nil }
func (m *mockStore) UpdatePayment(int, float64, string) (*Payment, error) { return nil, nil }
func (m *mockStore) UpsertStripePayment(p *Payment) (*Payment, error)     { return m.record(p) }
func (m *mockStore) FindStripePayment(id string) (*Payment, error) {
	if m.byStripeID == nil {
		return nil, gorm.ErrRecordNotFound
	}
	if p, ok := m.byStripeID[id]; ok {
		clone := *p
		return &clone, nil
	}
	return nil, gorm.ErrRecordNotFound
}

func TestSyncStripePayments(t *testing.T) {
	bookingID := uint(42)
	stripePayments := []stripeclient.Payment{
		{
			ID:            "pi_123",
			Amount:        120.50,
			PaymentMethod: "card",
			Status:        "succeeded",
			BookingID:     &bookingID,
		},
	}

	store := &mockStore{}
	stripe := &fakeStripeClient{payments: stripePayments}
	logger := slog.New(slog.DiscardHandler)

	svc, err := NewService(logger, store, nil, nil, stripe)
	if err != nil {
		t.Fatalf("NewService returned error: %v", err)
	}

	if err := svc.SyncStripePayments(context.Background(), time.Now().Add(-time.Hour), time.Now()); err != nil {
		t.Fatalf("SyncStripePayments returned error: %v", err)
	}

	if len(store.upserts) != 1 {
		t.Fatalf("expected 1 upsert, got %d", len(store.upserts))
	}

	upsert := store.upserts[0]
	if upsert.Amount != 120.50 {
		t.Errorf("unexpected amount: %v", upsert.Amount)
	}
	if upsert.PaymentMethod != config.PaymentMethod("Card") {
		t.Errorf("unexpected payment method: %v", upsert.PaymentMethod)
	}
	if upsert.StripePaymentID == nil || *upsert.StripePaymentID != "pi_123" {
		t.Errorf("stripe payment id not set correctly: %v", upsert.StripePaymentID)
	}
}

func TestSyncStripePaymentsSkipsMissingBooking(t *testing.T) {
	stripePayments := []stripeclient.Payment{
		{ID: "pi_123", Amount: 10},
	}

	store := &mockStore{}
	stripe := &fakeStripeClient{payments: stripePayments}
	logger := slog.New(slog.DiscardHandler)

	svc, err := NewService(logger, store, nil, nil, stripe)
	if err != nil {
		t.Fatalf("NewService returned error: %v", err)
	}

	if err := svc.SyncStripePayments(context.Background(), time.Now().Add(-time.Hour), time.Now()); err != nil {
		t.Fatalf("SyncStripePayments returned error: %v", err)
	}

	if len(store.upserts) != 0 {
		t.Fatalf("expected 0 upserts, got %d", len(store.upserts))
	}
}

func TestSyncStripePaymentsReturnsAggregatedError(t *testing.T) {
	bookingID := uint(7)
	stripePayments := []stripeclient.Payment{
		{
			ID:            "pi_err",
			Amount:        50,
			PaymentMethod: "card",
			Status:        "succeeded",
			BookingID:     &bookingID,
		},
	}

	store := &mockStore{err: errors.New("db failure")}
	stripe := &fakeStripeClient{payments: stripePayments}
	logger := slog.New(slog.DiscardHandler)

	svc, err := NewService(logger, store, nil, nil, stripe)
	if err != nil {
		t.Fatalf("NewService returned error: %v", err)
	}

	err = svc.SyncStripePayments(context.Background(), time.Now().Add(-time.Hour), time.Now())
	if err == nil {
		t.Fatalf("expected error, got nil")
	}
}
