package bootstrapimpl

import (
	"context"
	"database/sql"
	"fmt"

	bootstate "github.com/nexuscrm/db/pkg/bootstate"
)

// MigrationStamp helpers are the first users of `_System_Migration`, the
// "applied data migrations" table declared in system_tables.json. The schema
// existed before any callers needed it; PR #271 Round 9 introduces the first
// migration that requires a true one-shot guard (warm-upgrade-only data
// rewrite that cannot be safely re-run after the new authoring contract is
// live), so the helpers land alongside that migration's first use.
//
// Concurrency: callers run inside `ApplyAll`'s control-plane apply lock,
// so a single instance owns the writes; no extra locking is required here.
//
// Schema reference: backend/internal/bootstrap/system_tables.json (search
// for "_System_Migration"). Primary key Id is VARCHAR(255).
//
// Wave 5: the *sql.DB-shaped receipt store relocated to the neutral sibling
// github.com/nexuscrm/db/pkg/bootstate so the control plane reaches it without
// importing internal/bootstrap. MigrationApplied / RecordMigrationApplied are
// now thin aliases over the sibling. The unexported, interface-accepting
// recordMigrationApplied / migrationApplied stay here because
// NormalizeLegacyDDLDefaults stamps inside an open *sql.Tx (a migrationStampExecer
// that is not a *sql.DB). SQL is byte-for-byte identical across both copies.

type migrationStampQuerier interface {
	QueryRowContext(context.Context, string, ...any) *sql.Row
}

type migrationStampExecer interface {
	ExecContext(context.Context, string, ...any) (sql.Result, error)
}

// MigrationApplied reports whether migrationId already has a row in
// `_System_Migration`. The check is read-only and never inserts.
func MigrationApplied(ctx context.Context, db *sql.DB, migrationId string) (bool, error) {
	return bootstate.MigrationApplied(ctx, db, migrationId)
}

func migrationApplied(ctx context.Context, db migrationStampQuerier, migrationId string) (bool, error) {
	const selectSQL = `SELECT 1 FROM _System_Migration WHERE Id = ? LIMIT 1`
	row := db.QueryRowContext(ctx, selectSQL, migrationId)
	var present int
	err := row.Scan(&present)
	if err == sql.ErrNoRows {
		return false, nil
	}
	if err != nil {
		return false, fmt.Errorf("MigrationApplied(%s): %w", migrationId, err)
	}
	return true, nil
}

// RecordMigrationApplied inserts a stamp row marking migrationId as applied.
// Idempotent: re-recording the same migrationId is a no-op (ON DUPLICATE KEY
// UPDATE keeps the original AppliedAt so a second call cannot rewrite the
// historical record). Caller passes the migration's monotonically-increasing
// version (use 1 for the first revision; bump only when the migration's
// observable behavior changes such that re-running on stamped systems is
// required) and a short human-readable description for operator forensics.
func RecordMigrationApplied(ctx context.Context, db *sql.DB, migrationId string, version int, description string) error {
	return bootstate.RecordMigrationApplied(ctx, db, migrationId, version, description)
}

func recordMigrationApplied(ctx context.Context, db migrationStampExecer, migrationId string, version int, description string) error {
	const insertSQL = `INSERT INTO _System_Migration (Id, Version, Description, AppliedAt, CreatedDate, LastModifiedDate)
			VALUES (?, ?, ?, NOW(), NOW(), NOW())
			ON DUPLICATE KEY UPDATE LastModifiedDate = NOW()`
	if _, err := db.ExecContext(ctx, insertSQL, migrationId, version, description); err != nil {
		return fmt.Errorf("RecordMigrationApplied(%s): %w", migrationId, err)
	}
	return nil
}
