// Package catalogadmissionfence owns the single durable publication-admission
// fence used by short source-preparation and namespace-identity write
// transactions.
package catalogadmissionfence

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"strconv"
)

const Key = "CATALOG_PUBLICATION_VERSION"

var (
	ErrMissing  = errors.New("catalog admission fence: missing")
	ErrIdentity = errors.New("catalog admission fence: identity differs")
	ErrCorrupt  = errors.New("catalog admission fence: corrupt generation")
)

// LockExclusive serializes short source-preparation and namespace-identity
// writes so cross-table command identity is checked and reserved atomically.
func LockExclusive(ctx context.Context, tx *sql.Tx) (uint64, error) {
	return lock(ctx, tx)
}

func lock(
	ctx context.Context,
	tx *sql.Tx,
) (uint64, error) {
	var key string
	var rawGeneration []byte
	err := tx.QueryRowContext(ctx, `SELECT FenceKey, FenceGeneration
FROM _System_CatalogPublicationAdmissionFence
WHERE FenceKey = ?
FOR UPDATE`,
		Key,
	).Scan(&key, &rawGeneration)
	if errors.Is(err, sql.ErrNoRows) {
		return 0, ErrMissing
	}
	if err != nil {
		return 0, err
	}
	if key != Key {
		return 0, ErrIdentity
	}
	if len(rawGeneration) == 0 {
		return 0, ErrCorrupt
	}
	for _, character := range rawGeneration {
		if character < '0' || character > '9' {
			return 0, ErrCorrupt
		}
	}
	generation, err := strconv.ParseUint(string(rawGeneration), 10, 64)
	if err != nil || strconv.FormatUint(generation, 10) != string(rawGeneration) {
		return 0, fmt.Errorf("%w: non-canonical decimal", ErrCorrupt)
	}
	return generation, nil
}
