package mongo

import (
	"context"
	"time"

	"github.com/tech/sendico/payments/storage/model"
	quotestorage "github.com/tech/sendico/payments/storage/quote"
	"github.com/tech/sendico/payments/storage/quote/mongo/store"
	"github.com/tech/sendico/pkg/db"
	"github.com/tech/sendico/pkg/db/repository"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
)

// Store implements quotestorage.Repository backed by MongoDB.
type Store struct {
	logger mlogger.Logger
	ping   func(context.Context) error

	quotes quotestorage.QuotesStore
}

type options struct {
	quoteRetention time.Duration
}

// Option configures the Mongo-backed quotes repository.
type Option func(*options)

// WithQuoteRetention sets how long quote records are retained after expiry.
func WithQuoteRetention(retention time.Duration) Option {
	return func(opts *options) {
		opts.quoteRetention = retention
	}
}

// New constructs a Mongo-backed quotes repository from a Mongo connection.
func New(logger mlogger.Logger, conn *db.MongoConnection, opts ...Option) (*Store, error) {
	if conn == nil {
		return nil, merrors.InvalidArgument("payments.quote.storage.mongo: connection is nil")
	}
	quotesRepo := repository.CreateMongoRepository(conn.Database(), (&model.PaymentQuoteRecord{}).Collection())
	return NewWithRepository(logger, conn.Ping, quotesRepo, opts...)
}

// NewWithRepository constructs a quotes repository using the provided primitives.
func NewWithRepository(logger mlogger.Logger, ping func(context.Context) error, quotesRepo repository.Repository, opts ...Option) (*Store, error) {
	if ping == nil {
		return nil, merrors.InvalidArgument("payments.quote.storage.mongo: ping func is nil")
	}
	if quotesRepo == nil {
		return nil, merrors.InvalidArgument("payments.quote.storage.mongo: quotes repository is nil")
	}

	cfg := options{}
	for _, opt := range opts {
		if opt != nil {
			opt(&cfg)
		}
	}

	childLogger := logger.Named("quote_storage").Named("mongo")
	quotesStore, err := store.NewQuotes(childLogger, quotesRepo, cfg.quoteRetention)
	if err != nil {
		return nil, err
	}
	result := &Store{
		logger: childLogger,
		ping:   ping,
		quotes: quotesStore,
	}

	return result, nil
}

// Ping verifies connectivity with the backing database.
func (s *Store) Ping(ctx context.Context) error {
	if s.ping == nil {
		return merrors.InvalidArgument("payments.quote.storage.mongo: ping func is nil")
	}
	return s.ping(ctx)
}

// Quotes returns the quotes store.
func (s *Store) Quotes() quotestorage.QuotesStore {
	return s.quotes
}

var _ quotestorage.Repository = (*Store)(nil)
