package identity

import (
	"context"
	"database/sql"
	"strings"
	"testing"
	"time"

	"github.com/go-sql-driver/mysql"
	"github.com/stretchr/testify/require"

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

func TestFindOrCreateUserByPingCAPAuthInitializesProfileOnlyOnCreate(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	db := startIdentityTestPool(t, ctx)
	store := New(db, Options{})

	created, isNew, err := store.FindOrCreateUserByPingCAPAuth(ctx, PingCAPAuthUserInfo{
		OpenID:        "pingcap-new-user",
		DisplayName:   "PingCAP Initial Name",
		Email:         "pingcap-new@example.com",
		EmailVerified: true,
		AvatarURL:     "https://example.com/initial.png",
	})
	require.NoError(t, err)
	require.True(t, isNew)
	require.Equal(t, "PingCAP Initial Name", created.DisplayName)
	require.Equal(t, "https://example.com/initial.png", created.AvatarURL)

	updated, isNew, err := store.FindOrCreateUserByPingCAPAuth(ctx, PingCAPAuthUserInfo{
		OpenID:        "pingcap-new-user",
		DisplayName:   "PingCAP Later Name",
		Email:         "pingcap-new@example.com",
		EmailVerified: true,
		AvatarURL:     "https://example.com/later.png",
	})
	require.NoError(t, err)
	require.False(t, isNew)
	require.Equal(t, created.ID, updated.ID)
	require.Equal(t, "PingCAP Initial Name", updated.DisplayName)
	require.Equal(t, "https://example.com/initial.png", updated.AvatarURL)
}

func TestFindOrCreateUserByPingCAPAuthPreservesProfileWhenLinkingExistingUser(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	db := startIdentityTestPool(t, ctx)
	store := New(db, Options{})

	now := time.Now().UTC()
	userID := "usr_pingcap_link_existing"
	mustExec(t, db, `
	INSERT INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
	VALUES ($1, 'link-existing@example.com', 'link_existing', 'Local Name', 'https://example.com/local.png', FALSE, '', NULL, $2, $2)
	`, userID, now)

	linked, isNew, err := store.FindOrCreateUserByPingCAPAuth(ctx, PingCAPAuthUserInfo{
		OpenID:        "pingcap-link-existing",
		DisplayName:   "PingCAP Name",
		Email:         "link-existing@example.com",
		EmailVerified: true,
		AvatarURL:     "https://example.com/linked.png",
	})
	require.NoError(t, err)
	require.False(t, isNew)
	require.Equal(t, userID, linked.ID)
	require.Equal(t, "Local Name", linked.DisplayName)
	require.Equal(t, "https://example.com/local.png", linked.AvatarURL)

	var pingcapSub string
	require.NoError(t, db.QueryRow(ctx, `
	SELECT COALESCE(pingcap_sub, '')
	FROM users
	WHERE id = $1
	`, userID).Scan(&pingcapSub))
	require.Equal(t, "pingcap-link-existing", pingcapSub)
}

func TestFindOrCreateUserByPingCAPAuthRetriesNameDuplicateRace(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	tx := &pingcapAuthNameRaceTx{}
	store := New(&pingcapAuthNameRaceDB{tx: tx}, Options{})

	user, isNew, err := store.FindOrCreateUserByPingCAPAuth(ctx, PingCAPAuthUserInfo{
		OpenID:        "pingcap-name-race",
		DisplayName:   "raceuser",
		Email:         "race@example.com",
		EmailVerified: true,
	})
	require.NoError(t, err)
	require.True(t, isNew)
	require.Equal(t, "raceuser1", user.Name)
	require.Equal(t, []string{"raceuser", "raceuser1"}, tx.insertNames)
	require.True(t, tx.committed)
	require.False(t, tx.rolledBack)
}

type pingcapAuthNameRaceDB struct {
	tx *pingcapAuthNameRaceTx
}

func (db *pingcapAuthNameRaceDB) QueryRow(context.Context, string, ...any) coredb.Row {
	return pingcapAuthNameRaceRow{err: sql.ErrNoRows}
}

func (db *pingcapAuthNameRaceDB) Query(context.Context, string, ...any) (coredb.Rows, error) {
	return nil, nil
}

func (db *pingcapAuthNameRaceDB) Exec(context.Context, string, ...any) (coredb.Result, error) {
	return pingcapAuthNameRaceResult(0), nil
}

func (db *pingcapAuthNameRaceDB) BeginTx(context.Context, coredb.TxOptions) (coredb.Tx, error) {
	return db.tx, nil
}

type pingcapAuthNameRaceTx struct {
	insertNames []string
	committed   bool
	rolledBack  bool
}

func (tx *pingcapAuthNameRaceTx) QueryRow(_ context.Context, query string, args ...any) coredb.Row {
	switch {
	case strings.Contains(query, "FROM users") && strings.Contains(query, "pingcap_sub"):
		return pingcapAuthNameRaceRow{err: sql.ErrNoRows}
	case strings.Contains(query, "FROM users") && strings.Contains(query, "email"):
		return pingcapAuthNameRaceRow{err: sql.ErrNoRows}
	case strings.Contains(query, "SELECT EXISTS"):
		return pingcapAuthNameRaceRow{values: []any{false}}
	default:
		return pingcapAuthNameRaceRow{err: sql.ErrNoRows}
	}
}

func (tx *pingcapAuthNameRaceTx) Query(context.Context, string, ...any) (coredb.Rows, error) {
	return nil, nil
}

func (tx *pingcapAuthNameRaceTx) Exec(_ context.Context, query string, args ...any) (coredb.Result, error) {
	if !strings.Contains(query, "INSERT INTO users") {
		return pingcapAuthNameRaceResult(0), nil
	}
	name, _ := args[2].(string)
	tx.insertNames = append(tx.insertNames, name)
	if name == "raceuser" {
		return nil, &mysql.MySQLError{
			Number:  1062,
			Message: "Duplicate entry 'raceuser' for key 'name'",
		}
	}
	return pingcapAuthNameRaceResult(1), nil
}

func (tx *pingcapAuthNameRaceTx) Commit(context.Context) error {
	tx.committed = true
	return nil
}

func (tx *pingcapAuthNameRaceTx) Rollback(context.Context) error {
	tx.rolledBack = true
	return nil
}

type pingcapAuthNameRaceRow struct {
	values []any
	err    error
}

func (row pingcapAuthNameRaceRow) Scan(dest ...any) error {
	if row.err != nil {
		return row.err
	}
	if len(dest) != len(row.values) {
		return sql.ErrNoRows
	}
	for i, value := range row.values {
		switch typed := dest[i].(type) {
		case *bool:
			boolValue, ok := value.(bool)
			if !ok {
				return sql.ErrNoRows
			}
			*typed = boolValue
		default:
			return sql.ErrNoRows
		}
	}
	return nil
}

type pingcapAuthNameRaceResult int64

func (result pingcapAuthNameRaceResult) RowsAffected() int64 {
	return int64(result)
}
