package internal

import (
	"database/sql"
	"errors"
)

type DBHandleFactoryFunc = func() (*sql.DB, error)
type RepositoryFactoryFunc = func(db *sql.Tx) any

type TransactionProvider struct {
	db                          *sql.DB
	registrationRepositoryFunc  RepositoryFactoryFunc
	outboxRepositoryFactoryFunc RepositoryFactoryFunc
}

func NewTransactionProvider(db *sql.DB,
	registrationRepositoryFunc RepositoryFactoryFunc,
	outboxRepositoryFactoryFunc RepositoryFactoryFunc) *TransactionProvider {

	return &TransactionProvider{
		db:                          db,
		outboxRepositoryFactoryFunc: outboxRepositoryFactoryFunc,
		registrationRepositoryFunc:  registrationRepositoryFunc,
	}
}

func (p *TransactionProvider) Transact(txFunc func(adapters Repositories) error) error {
	return runInTx(p.db, func(tx *sql.Tx) error {
		adapters := Repositories{
			outboxRepo:       (p.outboxRepositoryFactoryFunc(tx)).(OutboxRepository),
			registrationRepo: (p.registrationRepositoryFunc(tx)).(RegistrationRepository),
		}

		return txFunc(adapters)
	})
}

func runInTx(db *sql.DB, fn func(tx *sql.Tx) error) error {
	tx, err := db.Begin()
	if err != nil {
		return err
	}

	err = fn(tx)
	if err == nil {
		return tx.Commit()
	}

	rollbackErr := tx.Rollback()
	if rollbackErr != nil {
		return errors.Join(err, rollbackErr)
	}

	return err
}
