package ports

import (
	"context"
	"sync"
)

type afterCommitHooksKey struct{}
type afterCommitHooksDisabled struct{}
type transactionCompletionHooksKey struct{}
type transactionCompletionHooksDisabled struct{}

type afterCommitHooks struct {
	mu    sync.Mutex
	hooks []func()
}

type TransactionCompletionOutcome string

const (
	TransactionCompletionCommitted     TransactionCompletionOutcome = "committed"
	TransactionCompletionRolledBack    TransactionCompletionOutcome = "rolled_back"
	TransactionCompletionCommitError   TransactionCompletionOutcome = "commit_error"
	TransactionCompletionRollbackError TransactionCompletionOutcome = "rollback_error"
)

type transactionCompletionHooks struct {
	mu    sync.Mutex
	hooks []func(TransactionCompletionOutcome)
}

// WithAfterCommitHooks attaches a fresh process-local after-commit hook
// collector to a transaction context. The transaction manager runs registered
// hooks only after that SQL transaction's commit succeeds.
func WithAfterCommitHooks(ctx context.Context) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	return context.WithValue(ctx, afterCommitHooksKey{}, &afterCommitHooks{})
}

// WithoutAfterCommitHooks strips any inherited collector from ctx. Detached
// independent transactions must not be able to consume an outer transaction's
// pending hooks before that outer transaction commits.
func WithoutAfterCommitHooks(ctx context.Context) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	return context.WithValue(ctx, afterCommitHooksKey{}, afterCommitHooksDisabled{})
}

// WithTransactionCompletionHooks attaches a fresh process-local transaction
// completion hook collector to a transaction context. The transaction manager
// runs registered hooks exactly once after the SQL transaction reaches a
// terminal outcome: committed, rolled back, commit error, or rollback error.
func WithTransactionCompletionHooks(ctx context.Context) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	return context.WithValue(ctx, transactionCompletionHooksKey{}, &transactionCompletionHooks{})
}

// WithoutTransactionCompletionHooks strips any inherited completion collector
// from ctx. Detached independent transactions must not consume an outer
// transaction's pending completion hooks.
func WithoutTransactionCompletionHooks(ctx context.Context) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	return context.WithValue(ctx, transactionCompletionHooksKey{}, transactionCompletionHooksDisabled{})
}

// RegisterAfterCommitHook registers hook on ctx's transaction collector. It
// returns false when ctx was not created by a transaction manager that supports
// after-commit hooks.
func RegisterAfterCommitHook(ctx context.Context, hook func()) bool {
	if hook == nil || ctx == nil {
		return false
	}
	hooks, _ := ctx.Value(afterCommitHooksKey{}).(*afterCommitHooks)
	if hooks == nil {
		return false
	}
	hooks.mu.Lock()
	hooks.hooks = append(hooks.hooks, hook)
	hooks.mu.Unlock()
	return true
}

// RunAfterCommitHooks consumes and runs hooks registered on ctx. Callers should
// invoke this only after commit succeeds; rollback paths intentionally skip it.
func RunAfterCommitHooks(ctx context.Context) {
	hooks, _ := ctx.Value(afterCommitHooksKey{}).(*afterCommitHooks)
	if hooks == nil {
		return
	}
	hooks.mu.Lock()
	toRun := append([]func(){}, hooks.hooks...)
	hooks.hooks = nil
	hooks.mu.Unlock()
	for _, hook := range toRun {
		hook()
	}
}

// RegisterTransactionCompletionHook registers hook on ctx's transaction
// completion collector. It returns false when ctx was not created by a
// transaction manager that supports completion hooks.
func RegisterTransactionCompletionHook(ctx context.Context, hook func(TransactionCompletionOutcome)) bool {
	if hook == nil || ctx == nil {
		return false
	}
	hooks, _ := ctx.Value(transactionCompletionHooksKey{}).(*transactionCompletionHooks)
	if hooks == nil {
		return false
	}
	hooks.mu.Lock()
	hooks.hooks = append(hooks.hooks, hook)
	hooks.mu.Unlock()
	return true
}

// RunTransactionCompletionHooks consumes and runs hooks registered on ctx.
// Callers invoke this after the SQL transaction reaches a terminal outcome.
func RunTransactionCompletionHooks(ctx context.Context, outcome TransactionCompletionOutcome) {
	hooks, _ := ctx.Value(transactionCompletionHooksKey{}).(*transactionCompletionHooks)
	if hooks == nil {
		return
	}
	hooks.mu.Lock()
	toRun := append([]func(TransactionCompletionOutcome){}, hooks.hooks...)
	hooks.hooks = nil
	hooks.mu.Unlock()
	for _, hook := range toRun {
		hook(outcome)
	}
}
