// Package autonumber — unit tests for queryLiveMaxForPrefix
// (Option B live-scan at lazy-init).
package autonumber

import (
	"context"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestQueryLiveMaxForPrefix_AbsorbsLegacyData pins the Codex round-3
// race-closure contract: an existing live-table record like
// "JP-QT-260418-07" in a bucket whose counter row does not yet exist
// must be reflected in liveMax so the lazy-init seed is 7 (+1 = 8
// handled by initialNextFor).
func TestQueryLiveMaxForPrefix_AbsorbsLegacyData(t *testing.T) {
	t.Parallel()

	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("INFORMATION_SCHEMA.COLUMNS").
		WithArgs("Quote", "Name").
		WillReturnRows(sqlmock.NewRows([]string{"COUNT(*)"}).AddRow(1))
	mock.ExpectQuery("SELECT `Name` FROM `Quote` WHERE `Name` LIKE CONCAT").
		WithArgs("JP-QT-260418-", "").
		WillReturnRows(sqlmock.NewRows([]string{"Name"}).
			AddRow("JP-QT-260418-01").
			AddRow("JP-QT-260418-07").
			AddRow("JP-QT-260418-03"))

	got, err := queryLiveMaxForPrefix(context.Background(), db,
		"Quote", "Name", "JP-QT-260418-", "")
	require.NoError(t, err)
	assert.Equal(t, int64(7), got, "max counter across 3 live rows")
	require.NoError(t, mock.ExpectationsWereMet())
}

// TestQueryLiveMaxForPrefix_CounterMiddle — Codex round-6 HIGH fix.
// For a format like `{Country}-{00}-END` the counter is NOT the final
// token; rendered live values look like `JP-01-END`, `JP-07-END`.
// The query must match both the before-counter and after-counter
// anchors. Before the fix, queryLiveMaxForPrefix used a single
// `LIKE 'before%'` which would miss these rows entirely and
// lazy-init at 1, duplicating an existing identifier.
func TestQueryLiveMaxForPrefix_CounterMiddle(t *testing.T) {
	t.Parallel()

	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("INFORMATION_SCHEMA.COLUMNS").
		WithArgs("Quote", "Name").
		WillReturnRows(sqlmock.NewRows([]string{"COUNT(*)"}).AddRow(1))
	mock.ExpectQuery("SELECT `Name` FROM `Quote` WHERE `Name` LIKE CONCAT").
		WithArgs("JP-", "-END").
		WillReturnRows(sqlmock.NewRows([]string{"Name"}).
			AddRow("JP-01-END").
			AddRow("JP-07-END").
			AddRow("JP-bogus-END"). // non-digit middle → skip
			AddRow("JP-03-END").
			AddRow("SOMETHING-ELSE")) // doesn't match anchors → skip

	got, err := queryLiveMaxForPrefix(context.Background(), db,
		"Quote", "Name", "JP-", "-END")
	require.NoError(t, err)
	assert.Equal(t, int64(7), got, "counter-middle format must extract digit middle correctly")
	require.NoError(t, mock.ExpectationsWereMet())
}

// TestQueryLiveMaxForPrefix_SkipsNonNumericSuffix — legacy random
// 8-digit-suffix data coexists with the new YYMMDD format. The new
// format's prefix is the YYMMDD-qualified bucket; legacy data is in
// a different (older, non-date-qualified) bucket so shouldn't match
// our LIKE in practice. But if it does (e.g. prefix overlap), we
// must defensively skip non-numeric suffixes.
func TestQueryLiveMaxForPrefix_SkipsNonNumericSuffix(t *testing.T) {
	t.Parallel()

	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("INFORMATION_SCHEMA.COLUMNS").
		WithArgs("Quote", "Name").
		WillReturnRows(sqlmock.NewRows([]string{"COUNT(*)"}).AddRow(1))
	mock.ExpectQuery("SELECT `Name` FROM `Quote` WHERE `Name` LIKE CONCAT").
		WithArgs("JP-QT-260418-", "").
		WillReturnRows(sqlmock.NewRows([]string{"Name"}).
			AddRow("JP-QT-260418-05").
			AddRow("JP-QT-260418-bogus"). // suffix not all digits → skip
			AddRow("JP-QT-260418-09"))

	got, err := queryLiveMaxForPrefix(context.Background(), db,
		"Quote", "Name", "JP-QT-260418-", "")
	require.NoError(t, err)
	assert.Equal(t, int64(9), got, "non-numeric suffix is ignored, not fail")
}

// TestQueryLiveMaxForPrefix_MissingColumnIsZero — fresh DB without
// the physical column returns 0 (caller falls back to startValue).
func TestQueryLiveMaxForPrefix_MissingColumnIsZero(t *testing.T) {
	t.Parallel()

	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("INFORMATION_SCHEMA.COLUMNS").
		WithArgs("NewObject", "NewField").
		WillReturnRows(sqlmock.NewRows([]string{"COUNT(*)"}).AddRow(0))

	got, err := queryLiveMaxForPrefix(context.Background(), db,
		"NewObject", "NewField", "X-Y-260418-", "")
	require.NoError(t, err)
	assert.Equal(t, int64(0), got)
}

// TestQueryLiveMaxForPrefix_IdentifierWhitelist rejects attempts to
// pass a SQL-injection-shaped object / field name.
func TestQueryLiveMaxForPrefix_IdentifierWhitelist(t *testing.T) {
	t.Parallel()

	db, _, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	_, err = queryLiveMaxForPrefix(context.Background(), db,
		"Quote; DROP TABLE Quote--", "Name", "JP-QT-260418-", "")
	require.Error(t, err)
	assert.Contains(t, err.Error(), "invalid objectName")

	_, err = queryLiveMaxForPrefix(context.Background(), db,
		"Quote", "Name'; --", "JP-QT-260418-", "")
	require.Error(t, err)
	assert.Contains(t, err.Error(), "invalid fieldName")
}

// TestQueryLiveMaxForPrefix_EmptyScopeKey — degenerate case, returns 0.
func TestQueryLiveMaxForPrefix_EmptyScopeKey(t *testing.T) {
	t.Parallel()

	db, _, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	got, err := queryLiveMaxForPrefix(context.Background(), db,
		"Quote", "Name", "", "")
	require.NoError(t, err)
	assert.Equal(t, int64(0), got)
}
