package mysql

import (
	"context"
	"regexp"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
)

func TestMigrateAddsLoginAuditClientVersionColumnsAndIndex(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("create sqlmock: %v", err)
	}
	defer db.Close()
	repository := &Repository{db: db}

	columnProbe := `(?s)SELECT COUNT\(\*\).*FROM information_schema\.COLUMNS.*TABLE_SCHEMA = DATABASE\(\).*TABLE_NAME = \?.*COLUMN_NAME = \?`
	mock.ExpectQuery(columnProbe).
		WithArgs("login_audit", "app_version").
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
	mock.ExpectExec(regexp.QuoteMeta("ALTER TABLE login_audit ADD COLUMN app_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '本次认证客户端版本' AFTER platform")).
		WillReturnResult(sqlmock.NewResult(0, 0))
	mock.ExpectQuery(columnProbe).
		WithArgs("login_audit", "build_number").
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
	mock.ExpectExec(regexp.QuoteMeta("ALTER TABLE login_audit ADD COLUMN build_number VARCHAR(64) NOT NULL DEFAULT '' COMMENT '本次认证客户端构建号' AFTER app_version")).
		WillReturnResult(sqlmock.NewResult(0, 0))
	for _, column := range []struct {
		table string
		name  string
	}{
		{table: "bd_profiles", name: "parent_owner_user_id"},
		{table: "role_invitations", name: "external_operator_user_id"},
		{table: "role_invitations", name: "parent_owner_user_id"},
		{table: "role_invitations", name: "parent_agency_id"},
	} {
		mock.ExpectQuery(columnProbe).
			WithArgs(column.table, column.name).
			WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
	}
	requiredIndexProbe := `(?s)SELECT COLUMN_NAME, NON_UNIQUE.*FROM information_schema\.STATISTICS.*TABLE_SCHEMA = DATABASE\(\).*TABLE_NAME = \?.*INDEX_NAME = \?.*ORDER BY SEQ_IN_INDEX`
	mock.ExpectQuery(requiredIndexProbe).
		WithArgs("bd_profiles", "idx_bd_profiles_parent_owner").
		WillReturnRows(sqlmock.NewRows([]string{"COLUMN_NAME", "NON_UNIQUE"}).
			AddRow("app_code", 1).
			AddRow("parent_owner_user_id", 1).
			AddRow("status", 1).
			AddRow("user_id", 1))
	mock.ExpectQuery(requiredIndexProbe).
		WithArgs("role_invitations", "idx_role_invitations_parent_owner").
		WillReturnRows(sqlmock.NewRows([]string{"COLUMN_NAME", "NON_UNIQUE"}).
			AddRow("app_code", 1).
			AddRow("parent_owner_user_id", 1).
			AddRow("status", 1).
			AddRow("created_at_ms", 1))
	for _, column := range []string{"token_family_id", "generation", "parent_session_id", "rotated_to_session_id", "rotation_at_ms", "rotation_request_id"} {
		mock.ExpectQuery(columnProbe).
			WithArgs("auth_sessions", column).
			WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
	}
	mock.ExpectQuery(requiredIndexProbe).
		WithArgs("auth_sessions", "idx_auth_sessions_token_family").
		WillReturnRows(sqlmock.NewRows([]string{"COLUMN_NAME", "NON_UNIQUE"}).
			AddRow("app_code", 1).
			AddRow("token_family_id", 1).
			AddRow("generation", 1))
	mock.ExpectExec(`(?s)CREATE TABLE IF NOT EXISTS auth_refresh_outcomes`).
		WillReturnResult(sqlmock.NewResult(0, 0))
	mock.ExpectExec(`(?s)CREATE TABLE IF NOT EXISTS auth_session_denylist_jobs`).
		WillReturnResult(sqlmock.NewResult(0, 0))
	mock.ExpectQuery(columnProbe).
		WithArgs("auth_session_denylist_jobs", "deny_until_ms").
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
	mock.ExpectQuery(requiredIndexProbe).
		WithArgs("auth_session_denylist_jobs", "uk_auth_session_denylist_job").
		WillReturnRows(sqlmock.NewRows([]string{"COLUMN_NAME", "NON_UNIQUE"}).
			AddRow("app_code", 0).
			AddRow("session_id", 0))
	indexProbe := `(?s)SELECT COLUMN_NAME.*FROM information_schema\.STATISTICS.*TABLE_SCHEMA = DATABASE\(\).*TABLE_NAME = \?.*INDEX_NAME = \?.*ORDER BY SEQ_IN_INDEX`
	mock.ExpectQuery(indexProbe).
		WithArgs("auth_session_denylist_jobs", "idx_auth_session_denylist_cleanup").
		WillReturnRows(sqlmock.NewRows([]string{"COLUMN_NAME"}).
			AddRow("deny_until_ms").
			AddRow("job_id"))
	mock.ExpectQuery(indexProbe).
		WithArgs("login_audit", "idx_login_audit_latest_success").
		WillReturnRows(sqlmock.NewRows([]string{"COLUMN_NAME"}))
	mock.ExpectExec(regexp.QuoteMeta("ALTER TABLE login_audit ADD INDEX idx_login_audit_latest_success (app_code, user_id, result, blocked, login_type, created_at_ms, id)")).
		WillReturnResult(sqlmock.NewResult(0, 0))

	if err := repository.Migrate(context.Background()); err != nil {
		t.Fatalf("migrate login audit client version schema: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("migration expectations mismatch: %v", err)
	}
}
