package mongo

import (
	"context"

	"github.com/tech/sendico/payments/orchestrator/storage"
	"github.com/tech/sendico/payments/orchestrator/storage/model"
	"github.com/tech/sendico/payments/orchestrator/storage/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 storage.Repository backed by MongoDB.
type Store struct {
	logger mlogger.Logger
	ping   func(context.Context) error

	payments storage.PaymentsStore
	quotes   storage.QuotesStore
	routes   storage.RoutesStore
	plans    storage.PlanTemplatesStore
}

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

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

	childLogger := logger.Named("storage").Named("mongo")
	paymentsStore, err := store.NewPayments(childLogger, paymentsRepo)
	if err != nil {
		return nil, err
	}
	quotesStore, err := store.NewQuotes(childLogger, quotesRepo)
	if err != nil {
		return nil, err
	}
	routesStore, err := store.NewRoutes(childLogger, routesRepo)
	if err != nil {
		return nil, err
	}
	plansStore, err := store.NewPlanTemplates(childLogger, plansRepo)
	if err != nil {
		return nil, err
	}
	result := &Store{
		logger:   childLogger,
		ping:     ping,
		payments: paymentsStore,
		quotes:   quotesStore,
		routes:   routesStore,
		plans:    plansStore,
	}

	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.storage.mongo: ping func is nil")
	}
	return s.ping(ctx)
}

// Payments returns the payments store.
func (s *Store) Payments() storage.PaymentsStore {
	return s.payments
}

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

// Routes returns the routing store.
func (s *Store) Routes() storage.RoutesStore {
	return s.routes
}

// PlanTemplates returns the plan templates store.
func (s *Store) PlanTemplates() storage.PlanTemplatesStore {
	return s.plans
}

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