package auth_test

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/hhs/camtalk/internal/auth"
	"github.com/hhs/camtalk/internal/store"
)

// newTestService 创建测试用的 AuthService + MemUserRepository。
func newTestService(t *testing.T) (auth.Service, *store.MemUserRepository) {
	t.Helper()
	tm := auth.NewTokenManager("test-secret", 15*time.Minute, 7*24*time.Hour)
	repo := store.NewMemUserRepository()
	svc := auth.NewAuthService(tm, repo)
	return svc, repo
}

// --- Register ---

func TestRegister_Success(t *testing.T) {
	svc, _ := newTestService(t)
	ctx := context.Background()

	resp, err := svc.Register(ctx, auth.RegisterRequest{
		Username: "alice",
		Password: "password123",
	})
	require.NoError(t, err)
	assert.NotEmpty(t, resp.User.ID)
	assert.Equal(t, "alice", resp.User.Username)
	assert.NotEmpty(t, resp.AccessToken)
	assert.NotEmpty(t, resp.RefreshToken)
}

func TestRegister_DuplicateUsername(t *testing.T) {
	svc, _ := newTestService(t)
	ctx := context.Background()

	_, err := svc.Register(ctx, auth.RegisterRequest{
		Username: "alice",
		Password: "password123",
	})
	require.NoError(t, err)

	// 同名再次注册
	_, err = svc.Register(ctx, auth.RegisterRequest{
		Username: "alice",
		Password: "another-password",
	})
	assert.ErrorIs(t, err, auth.ErrUsernameTaken)
}

// --- Login ---

func TestLogin_Success(t *testing.T) {
	svc, _ := newTestService(t)
	ctx := context.Background()

	// 先注册
	_, err := svc.Register(ctx, auth.RegisterRequest{
		Username: "bob",
		Password: "password123",
	})
	require.NoError(t, err)

	// 登录
	resp, err := svc.Login(ctx, auth.LoginRequest{
		Username: "bob",
		Password: "password123",
	})
	require.NoError(t, err)
	assert.Equal(t, "bob", resp.User.Username)
	assert.NotEmpty(t, resp.AccessToken)
	assert.NotEmpty(t, resp.RefreshToken)
}

func TestLogin_WrongPassword(t *testing.T) {
	svc, _ := newTestService(t)
	ctx := context.Background()

	_, err := svc.Register(ctx, auth.RegisterRequest{
		Username: "bob",
		Password: "password123",
	})
	require.NoError(t, err)

	_, err = svc.Login(ctx, auth.LoginRequest{
		Username: "bob",
		Password: "wrong-password",
	})
	assert.ErrorIs(t, err, auth.ErrInvalidCredentials)
}

func TestLogin_UserNotFound(t *testing.T) {
	svc, _ := newTestService(t)
	ctx := context.Background()

	_, err := svc.Login(ctx, auth.LoginRequest{
		Username: "nonexistent",
		Password: "password123",
	})
	assert.ErrorIs(t, err, auth.ErrInvalidCredentials)
}

// --- Refresh ---

func TestRefresh_Success(t *testing.T) {
	svc, _ := newTestService(t)
	ctx := context.Background()

	// 注册
	regResp, err := svc.Register(ctx, auth.RegisterRequest{
		Username: "charlie",
		Password: "password123",
	})
	require.NoError(t, err)

	// 刷新
	refreshResp, err := svc.Refresh(ctx, auth.RefreshRequest{
		RefreshToken: regResp.RefreshToken,
	})
	require.NoError(t, err)
	assert.Equal(t, "charlie", refreshResp.User.Username)
	assert.NotEmpty(t, refreshResp.AccessToken)
	assert.NotEmpty(t, refreshResp.RefreshToken)
	// 新旧 refresh token 应不同（rotation）
	assert.NotEqual(t, regResp.RefreshToken, refreshResp.RefreshToken)
}

func TestRefresh_UsedTokenFails(t *testing.T) {
	svc, _ := newTestService(t)
	ctx := context.Background()

	regResp, err := svc.Register(ctx, auth.RegisterRequest{
		Username: "charlie",
		Password: "password123",
	})
	require.NoError(t, err)

	// 第一次刷新
	_, err = svc.Refresh(ctx, auth.RefreshRequest{
		RefreshToken: regResp.RefreshToken,
	})
	require.NoError(t, err)

	// 用旧 token 再次刷新 → 应失败
	_, err = svc.Refresh(ctx, auth.RefreshRequest{
		RefreshToken: regResp.RefreshToken,
	})
	assert.ErrorIs(t, err, auth.ErrRefreshTokenUsed)
}

func TestRefresh_InvalidToken(t *testing.T) {
	svc, _ := newTestService(t)
	ctx := context.Background()

	_, err := svc.Refresh(ctx, auth.RefreshRequest{
		RefreshToken: "completely-invalid-token",
	})
	assert.ErrorIs(t, err, auth.ErrRefreshTokenUsed)
}

// --- Logout ---

func TestLogout_Success(t *testing.T) {
	svc, _ := newTestService(t)
	ctx := context.Background()

	regResp, err := svc.Register(ctx, auth.RegisterRequest{
		Username: "dave",
		Password: "password123",
	})
	require.NoError(t, err)

	// 登出
	err = svc.Logout(ctx, regResp.User.ID, regResp.RefreshToken)
	require.NoError(t, err)

	// 登出后 refresh token 应失效
	_, err = svc.Refresh(ctx, auth.RefreshRequest{
		RefreshToken: regResp.RefreshToken,
	})
	assert.ErrorIs(t, err, auth.ErrRefreshTokenUsed)
}

// --- Refresh Token 复用检测 ---

func TestRefresh_ReuseDetectedRevokesAllTokens(t *testing.T) {
	svc, repo := newTestService(t)
	ctx := context.Background()

	// 注册，获得令牌对 A
	regResp, err := svc.Register(ctx, auth.RegisterRequest{
		Username: "eve",
		Password: "password123",
	})
	require.NoError(t, err)
	tokenPairA_refresh := regResp.RefreshToken

	// 再次登录，获得令牌对 B
	loginResp, err := svc.Login(ctx, auth.LoginRequest{
		Username: "eve",
		Password: "password123",
	})
	require.NoError(t, err)
	tokenPairB_refresh := loginResp.RefreshToken

	// 用令牌对 A 的 refresh token 正常刷新 → 成功
	refreshResp, err := svc.Refresh(ctx, auth.RefreshRequest{
		RefreshToken: tokenPairA_refresh,
	})
	require.NoError(t, err)
	assert.NotEmpty(t, refreshResp.AccessToken)

	// 用令牌对 A 的旧 refresh token 再次刷新 → 复用检测，应失败
	_, err = svc.Refresh(ctx, auth.RefreshRequest{
		RefreshToken: tokenPairA_refresh,
	})
	assert.ErrorIs(t, err, auth.ErrRefreshTokenUsed)

	// 令牌对 B 的 refresh token 也应被吊销（全量吊销）
	_, err = svc.Refresh(ctx, auth.RefreshRequest{
		RefreshToken: tokenPairB_refresh,
	})
	assert.ErrorIs(t, err, auth.ErrRefreshTokenUsed)

	// 确认 DB 中该用户已无 refresh token
	_ = repo // repo 用于确认，但 MemUserRepository 无直接查询方法，通过 Refresh 失败已间接验证
}
