package mongo

import (
	"context"
	"time"

	"github.com/tech/sendico/ledger/storage"
	"github.com/tech/sendico/ledger/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"
)

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

	accounts       storage.AccountsStore
	journalEntries storage.JournalEntriesStore
	postingLines   storage.PostingLinesStore
	balances       storage.BalancesStore
	outbox         storage.OutboxStore
}

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 not initialised")
	}

	db := conn.Database()
	txFactory := newMongoTransactionFactory(client)

	s := &Store{
		logger:    logger.Named("storage").Named("mongo"),
		conn:      conn,
		db:        db,
		txFactory: txFactory,
	}

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

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

	// Initialize stores
	accountsStore, err := store.NewAccounts(s.logger, db)
	if err != nil {
		s.logger.Error("Failed to initialize accounts store", zap.Error(err))
		return nil, err
	}

	journalEntriesStore, err := store.NewJournalEntries(s.logger, db)
	if err != nil {
		s.logger.Error("Failed to initialize journal entries store", zap.Error(err))
		return nil, err
	}

	postingLinesStore, err := store.NewPostingLines(s.logger, db)
	if err != nil {
		s.logger.Error("Failed to initialize posting lines store", zap.Error(err))
		return nil, err
	}

	balancesStore, err := store.NewBalances(s.logger, db)
	if err != nil {
		s.logger.Error("Failed to initialize balances store", zap.Error(err))
		return nil, err
	}

	outboxStore, err := store.NewOutbox(s.logger, db)
	if err != nil {
		s.logger.Error("Failed to initialize outbox store", zap.Error(err))
		return nil, err
	}

	s.accounts = accountsStore
	s.journalEntries = journalEntriesStore
	s.postingLines = postingLinesStore
	s.balances = balancesStore
	s.outbox = outboxStore

	s.logger.Info("Ledger MongoDB storage initialized")

	return s, nil
}

func (s *Store) Ping(ctx context.Context) error {
	return s.conn.Ping(ctx)
}

func (s *Store) Accounts() storage.AccountsStore {
	return s.accounts
}

func (s *Store) JournalEntries() storage.JournalEntriesStore {
	return s.journalEntries
}

func (s *Store) PostingLines() storage.PostingLinesStore {
	return s.postingLines
}

func (s *Store) Balances() storage.BalancesStore {
	return s.balances
}

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

func (s *Store) Database() *mongo.Database {
	return s.db
}

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

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