package coredb

import (
	"context"
	"errors"
	"fmt"
)

// WithTx runs fn inside a default read-write transaction.
//
// It commits when fn returns nil, rolls back when fn returns an error, and
// rolls back before re-panicking when fn panics.
func WithTx(ctx context.Context, db TxBeginner, fn func(Tx) error) error {
	return WithTxOptions(ctx, db, TxOptions{}, fn)
}

// WithTxOptions runs fn inside a transaction created with options.
func WithTxOptions(ctx context.Context, db TxBeginner, options TxOptions, fn func(Tx) error) (err error) {
	if db == nil {
		return fmt.Errorf("coredb tx beginner is nil")
	}
	if fn == nil {
		return fmt.Errorf("coredb tx function is nil")
	}

	tx, err := db.BeginTx(ctx, options)
	if err != nil {
		return fmt.Errorf("begin tx: %w", err)
	}

	committed := false
	defer func() {
		if recovered := recover(); recovered != nil {
			_ = tx.Rollback(ctx)
			panic(recovered)
		}
		if !committed {
			if rollbackErr := tx.Rollback(ctx); err != nil && rollbackErr != nil {
				err = errors.Join(err, fmt.Errorf("rollback tx: %w", rollbackErr))
			}
		}
	}()

	if err = fn(tx); err != nil {
		return err
	}
	if commitErr := tx.Commit(ctx); commitErr != nil {
		err = fmt.Errorf("commit tx: %w", commitErr)
		return err
	}
	committed = true
	return nil
}
