package identity

import (
	"context"
	"errors"
	"testing"
	"time"

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

	"github.com/tidbcloud/AgentTeam/internal/core/authidentity"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

// TestExchangeMiniappPairingCodeForSessionRollsBackOnSessionInsertFailure
// proves the regression fix: if anything after the FOR UPDATE lock fails
// (here, the session INSERT), the whole transaction rolls back. The pairing
// code is NOT consumed (no used_at update committed), so the miniapp can
// retry the same code.
func TestExchangeMiniappPairingCodeForSessionRollsBackOnSessionInsertFailure(t *testing.T) {
	t.Parallel()

	sqlDB, mock, err := sqlmock.New()
	require.NoError(t, err)
	t.Cleanup(func() { _ = sqlDB.Close() })

	now := time.Now().UTC()

	mock.MatchExpectationsInOrder(true)
	mock.ExpectBegin()
	mock.ExpectQuery("SELECT user_id, workspace_id, server_url .* FOR UPDATE").
		WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg()).
		WillReturnRows(sqlmock.NewRows([]string{"user_id", "workspace_id", "server_url"}).
			AddRow("usr_1", "wsp_1", "https://example.com"))
	mock.ExpectQuery("SELECT id FROM users WHERE id = \\? FOR UPDATE").
		WithArgs("usr_1").
		WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("usr_1"))
	mock.ExpectQuery("SELECT name FROM workspaces").
		WithArgs("wsp_1").
		WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("My Workspace"))
	mock.ExpectQuery("SELECT id, COALESCE\\(email").
		WithArgs("usr_1").
		WillReturnRows(sqlmock.NewRows([]string{
			"id", "email", "mobile", "name", "display_name", "avatar_url", "is_admin",
			"email_verified_at", "mobile_verified_at", "created_at", "updated_at",
		}).AddRow(
			"usr_1", "alice@example.com", "", "alice", "Alice", "", false,
			nil, nil, now, now,
		))
	mock.ExpectExec("UPDATE user_session_families").
		WillReturnResult(sqlmock.NewResult(0, 0))
	mock.ExpectQuery("SELECT id FROM user_session_families").
		WithArgs("usr_1", sqlmock.AnyArg()).
		WillReturnRows(sqlmock.NewRows([]string{"id"}))
	mock.ExpectExec("INSERT INTO user_session_families").
		WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectExec("INSERT INTO user_sessions").
		WillReturnError(errors.New("simulated session insert failure"))
	mock.ExpectRollback()
	// used_at UPDATE must NOT be attempted after the session insert fails.
	// Expectations stop at the rollback — no ExpectExec for used_at.

	store := New(coredb.WrapSQLDB(sqlDB), Options{})
	_, err = store.ExchangeMiniappPairingCodeForSession(
		context.Background(), "some-code", authidentity.MiniappPairingClient{
			InstallationID: "ins_test", DeviceName: "Test phone", DevicePlatform: "ios",
		}, time.Hour,
	)
	require.Error(t, err)
	require.Contains(t, err.Error(), "simulated session insert failure")
	require.NoError(t, mock.ExpectationsWereMet())
}

// TestExchangeMiniappPairingCodeForSessionRequiresPositiveTTL guards against
// misconfiguration (zero or negative TTL would issue already-expired sessions).
func TestExchangeMiniappPairingCodeForSessionRequiresPositiveTTL(t *testing.T) {
	t.Parallel()

	sqlDB, _, err := sqlmock.New()
	require.NoError(t, err)
	t.Cleanup(func() { _ = sqlDB.Close() })

	store := New(coredb.WrapSQLDB(sqlDB), Options{})
	_, err = store.ExchangeMiniappPairingCodeForSession(
		context.Background(), "some-code", authidentity.MiniappPairingClient{
			InstallationID: "ins_test",
		}, 0,
	)
	require.Error(t, err)
}
