package mongo

import (
	"context"
	"time"

	"github.com/tech/sendico/gateway/chsettle/storage"
	"github.com/tech/sendico/gateway/chsettle/storage/mongo/store"
	gatewayoutbox "github.com/tech/sendico/gateway/common/outbox"
	"github.com/tech/sendico/pkg/db"
	"github.com/tech/sendico/pkg/db/transaction"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.uber.org/zap"
)

type Repository struct {
	logger    mlogger.Logger
	conn      *db.MongoConnection
	db        *mongo.Database
	txFactory transaction.Factory

	payments storage.PaymentsStore
	tg       storage.TelegramConfirmationsStore
	pending  storage.PendingConfirmationsStore
	treasury storage.TreasuryRequestsStore
	users    storage.TreasuryTelegramUsersStore
	outbox   gatewayoutbox.Store
}

func New(logger mlogger.Logger, conn *db.MongoConnection) (*Repository, error) {
	if logger == nil {
		logger = zap.NewNop()
	}
	if conn == nil {
		return nil, merrors.InvalidArgument("mongo connection is nil")
	}
	client := conn.Client()
	if client == nil {
		return nil, merrors.Internal("mongo client is not initialised")
	}
	db := conn.Database()
	if db == nil {
		return nil, merrors.Internal("mongo database is not initialised")
	}
	dbName := db.Name()
	logger = logger.Named("storage").Named("mongo")
	if dbName != "" {
		logger = logger.With(zap.String("database", dbName))
	}
	result := &Repository{
		logger:    logger,
		conn:      conn,
		db:        db,
		txFactory: newMongoTransactionFactory(client),
	}
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := result.conn.Ping(ctx); err != nil {
		result.logger.Error("Mongo ping failed during repository initialisation", zap.Error(err))
		return nil, err
	}
	paymentsStore, err := store.NewPayments(result.logger, result.db)
	if err != nil {
		result.logger.Error("Failed to initialise payments store", zap.Error(err), zap.String("store", "payments"))
		return nil, err
	}
	tgStore, err := store.NewTelegramConfirmations(result.logger, result.db)
	if err != nil {
		result.logger.Error("Failed to initialise telegram confirmations store", zap.Error(err), zap.String("store", "telegram_confirmations"))
		return nil, err
	}
	pendingStore, err := store.NewPendingConfirmations(result.logger, result.db)
	if err != nil {
		result.logger.Error("Failed to initialise pending confirmations store", zap.Error(err), zap.String("store", "pending_confirmations"))
		return nil, err
	}
	treasuryStore, err := store.NewTreasuryRequests(result.logger, result.db)
	if err != nil {
		result.logger.Error("Failed to initialise treasury requests store", zap.Error(err), zap.String("store", "treasury_requests"))
		return nil, err
	}
	treasuryUsersStore, err := store.NewTreasuryTelegramUsers(result.logger, result.db)
	if err != nil {
		result.logger.Error("Failed to initialise treasury telegram users store", zap.Error(err), zap.String("store", "treasury_telegram_users"))
		return nil, err
	}
	outboxStore, err := gatewayoutbox.NewMongoStore(result.logger, result.db)
	if err != nil {
		result.logger.Error("Failed to initialise outbox store", zap.Error(err), zap.String("store", "outbox"))
		return nil, err
	}
	result.payments = paymentsStore
	result.tg = tgStore
	result.pending = pendingStore
	result.treasury = treasuryStore
	result.users = treasuryUsersStore
	result.outbox = outboxStore
	result.logger.Info("Payment gateway MongoDB storage initialised")
	return result, nil
}

func (r *Repository) Payments() storage.PaymentsStore {
	return r.payments
}

func (r *Repository) TelegramConfirmations() storage.TelegramConfirmationsStore {
	return r.tg
}

func (r *Repository) PendingConfirmations() storage.PendingConfirmationsStore {
	return r.pending
}

func (r *Repository) TreasuryRequests() storage.TreasuryRequestsStore {
	return r.treasury
}

func (r *Repository) TreasuryTelegramUsers() storage.TreasuryTelegramUsersStore {
	return r.users
}

func (r *Repository) Outbox() gatewayoutbox.Store {
	return r.outbox
}

func (r *Repository) TransactionFactory() transaction.Factory {
	return r.txFactory
}

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