package mongo

import (
	"context"
	"time"

	gatewayoutbox "github.com/tech/sendico/gateway/common/outbox"
	"github.com/tech/sendico/gateway/tron/storage"
	"github.com/tech/sendico/gateway/tron/storage/mongo/store"
	"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"
)

// Store implements storage.Repository backed by MongoDB.
type Store struct {
	logger    mlogger.Logger
	conn      *db.MongoConnection
	db        *mongo.Database
	txFactory transaction.Factory

	wallets   storage.WalletsStore
	transfers storage.TransfersStore
	deposits  storage.DepositsStore
	outbox    gatewayoutbox.Store
}

// New creates a new Mongo-backed repository.
func New(logger mlogger.Logger, conn *db.MongoConnection) (*Store, error) {
	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")
	}

	result := &Store{
		logger:    logger.Named("storage").Named("mongo"),
		conn:      conn,
		db:        conn.Database(),
		txFactory: newMongoTransactionFactory(client),
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := result.Ping(ctx); err != nil {
		result.logger.Error("Mongo ping failed during repository initialisation", zap.Error(err))
		return nil, err
	}

	walletsStore, err := store.NewWallets(result.logger, result.db)
	if err != nil {
		result.logger.Error("Failed to initialise wallets store", zap.Error(err))
		return nil, err
	}
	transfersStore, err := store.NewTransfers(result.logger, result.db)
	if err != nil {
		result.logger.Error("Failed to initialise transfers store", zap.Error(err))
		return nil, err
	}
	depositsStore, err := store.NewDeposits(result.logger, result.db)
	if err != nil {
		result.logger.Error("Failed to initialise deposits store", zap.Error(err))
		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))
		return nil, err
	}

	result.wallets = walletsStore
	result.transfers = transfersStore
	result.deposits = depositsStore
	result.outbox = outboxStore

	result.logger.Info("Chain gateway MongoDB storage initialised")
	return result, nil
}

// Ping verifies the MongoDB connection.
func (s *Store) Ping(ctx context.Context) error {
	if s.conn == nil {
		return merrors.InvalidArgument("mongo connection is nil")
	}
	return s.conn.Ping(ctx)
}

// Wallets returns the wallets store.
func (s *Store) Wallets() storage.WalletsStore {
	return s.wallets
}

// Transfers returns the transfers store.
func (s *Store) Transfers() storage.TransfersStore {
	return s.transfers
}

// Deposits returns the deposits store.
func (s *Store) Deposits() storage.DepositsStore {
	return s.deposits
}

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

func (s *Store) TransactionFactory() transaction.Factory {
	return s.txFactory
}

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