package usecase

import (
	"backend/internal/config"
	"backend/pkg/permission/domain/entity"
	"backend/pkg/permission/domain/token"
	mockRepo "backend/pkg/permission/mock/repository"
	"context"
	"errors"
	"fmt"
	"testing"
	"time"

	"github.com/golang-jwt/jwt/v4"
	"github.com/stretchr/testify/assert"
	"go.uber.org/mock/gomock"
)

func TestTokenUseCase_NewToken(t *testing.T) {
	ctx := context.Background()

	tests := []struct {
		name      string
		req       entity.AuthorizationReq
		mockSetup func(*mockRepo.MockTokenRepository)
		wantErr   bool
	}{
		{
			name: "成功創建 Access Token",
			req: entity.AuthorizationReq{
				GrantType: "client_credentials",
				Scope:     "read",
				DeviceID:  "device123",
				Account:   "user123",
				Role:      "admin",
				Data: map[string]string{
					"uid": "user123",
				},
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().Create(ctx, gomock.Any()).Return(nil)
			},
			wantErr: false,
		},
		{
			name: "成功創建 Refresh Token",
			req: entity.AuthorizationReq{
				GrantType:      "client_credentials",
				Scope:          "read",
				DeviceID:       "device123",
				Account:        "user123",
				Role:           "admin",
				IsRefreshToken: true,
				Data: map[string]string{
					"uid": "user123",
				},
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().Create(ctx, gomock.Any()).Return(nil)
			},
			wantErr: false,
		},
		{
			name: "創建失敗 - Repository 錯誤",
			req: entity.AuthorizationReq{
				GrantType: "client_credentials",
				Scope:     "read",
				Account:   "user123",
				Role:      "admin",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().Create(ctx, gomock.Any()).Return(errors.New("db error"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockTokenRepo := mockRepo.NewMockTokenRepository(mockCtrl)
			tt.mockSetup(mockTokenRepo)

			uc := MustTokenUseCase(TokenUseCaseParam{
				TokenRepo: mockTokenRepo,
				Config: &config.Config{
					Token: struct {
						AccessSecret       string
						RefreshSecret      string
						AccessTokenExpiry  time.Duration
						RefreshTokenExpiry time.Duration
						OneTimeTokenExpiry time.Duration
						MaxTokensPerUser   int
						MaxTokensPerDevice int
					}{
						AccessSecret:       "test-secret",
						AccessTokenExpiry:  time.Hour,
						RefreshTokenExpiry: time.Hour * 24,
						RefreshSecret:      "refresh-secret",
						OneTimeTokenExpiry: time.Minute * 5,
						MaxTokensPerUser:   10,
						MaxTokensPerDevice: 5,
					},
				},
			})

			resp, err := uc.NewToken(ctx, tt.req)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.NotEmpty(t, resp.AccessToken)
				assert.Equal(t, token.TypeBearer.String(), resp.TokenType)
				assert.Greater(t, resp.ExpiresIn, int64(0))
				if tt.req.IsRefreshToken {
					assert.NotEmpty(t, resp.RefreshToken)
				}
			}
		})
	}
}

func TestTokenUseCase_RefreshToken(t *testing.T) {
	ctx := context.Background()
	now := time.Now().UTC()
	expires := now.Add(time.Hour).Unix()

	// 創建一個測試用的 token
	testToken := entity.Token{
		ID:               "test-id",
		UID:              "user123",
		DeviceID:         "device123",
		AccessToken:      "",
		RefreshToken:     "refresh-token",
		ExpiresIn:        int(expires),
		RefreshExpiresIn: int(now.Add(time.Hour * 24).Unix()),
		AccessCreateAt:   now,
		RefreshCreateAt:  now,
	}

	// 生成實際的 JWT token
	claims := entity.Claims{
		Data: map[string]string{
			"id":       testToken.ID,
			"uid":      "user123",
			"role":     "admin",
			"scope":    "read",
			"account":  "user123",
			"deviceId": "device123",
		},
		RegisteredClaims: jwt.RegisteredClaims{
			ID:        testToken.ID,
			ExpiresAt: jwt.NewNumericDate(time.Unix(expires, 0)),
			Issuer:    "permission",
		},
	}
	accessToken, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte("test-secret"))
	testToken.AccessToken = accessToken

	tests := []struct {
		name      string
		req       entity.RefreshTokenReq
		mockSetup func(*mockRepo.MockTokenRepository)
		wantErr   bool
	}{
		{
			name: "成功刷新 Token",
			req: entity.RefreshTokenReq{
				Token:    "refresh-token",
				DeviceID: "device123",
				Expires:  0,
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokenByOneTimeToken(ctx, "refresh-token").
					Return(testToken, nil)
				mockTokenRepo.EXPECT().Create(ctx, gomock.Any()).Return(nil)
				mockTokenRepo.EXPECT().Delete(ctx, testToken).Return(nil)
			},
			wantErr: false,
		},
		{
			name: "刷新失敗 - Token 不存在",
			req: entity.RefreshTokenReq{
				Token:    "invalid-token",
				DeviceID: "device123",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokenByOneTimeToken(ctx, "invalid-token").
					Return(entity.Token{}, errors.New("token not found"))
			},
			wantErr: true,
		},
		{
			name: "刷新失敗 - 創建新 Token 失敗",
			req: entity.RefreshTokenReq{
				Token:    "refresh-token",
				DeviceID: "device123",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokenByOneTimeToken(ctx, "refresh-token").
					Return(testToken, nil)
				mockTokenRepo.EXPECT().Create(ctx, gomock.Any()).Return(errors.New("create error"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockTokenRepo := mockRepo.NewMockTokenRepository(mockCtrl)
			tt.mockSetup(mockTokenRepo)

			uc := MustTokenUseCase(TokenUseCaseParam{
				TokenRepo: mockTokenRepo,
				Config: &config.Config{
					Token: struct {
						AccessSecret       string
						RefreshSecret      string
						AccessTokenExpiry  time.Duration
						RefreshTokenExpiry time.Duration
						OneTimeTokenExpiry time.Duration
						MaxTokensPerUser   int
						MaxTokensPerDevice int
					}{
						AccessSecret:       "test-secret",
						AccessTokenExpiry:  time.Hour,
						RefreshTokenExpiry: time.Hour * 24,
						RefreshSecret:      "refresh-secret",
						OneTimeTokenExpiry: time.Minute * 5,
						MaxTokensPerUser:   10,
						MaxTokensPerDevice: 5,
					},
				},
			})

			resp, err := uc.RefreshToken(ctx, tt.req)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.NotEmpty(t, resp.Token)
				assert.NotEmpty(t, resp.OneTimeToken)
				assert.Greater(t, resp.ExpiresIn, int64(0))
			}
		})
	}
}

func TestTokenUseCase_ValidationToken(t *testing.T) {
	ctx := context.Background()
	now := time.Now().UTC()
	expires := now.Add(time.Hour).Unix()

	testToken := entity.Token{
		ID:               "test-id",
		UID:              "user123",
		DeviceID:         "device123",
		ExpiresIn:        int(expires),
		RefreshExpiresIn: int(now.Add(time.Hour * 24).Unix()),
		AccessCreateAt:   now,
		RefreshCreateAt:  now,
	}

	claims := entity.Claims{
		Data: map[string]string{
			"id":      testToken.ID,
			"uid":     "user123",
			"role":    "admin",
			"scope":   "read",
			"account": "user123",
		},
		RegisteredClaims: jwt.RegisteredClaims{
			ID:        testToken.ID,
			ExpiresAt: jwt.NewNumericDate(time.Unix(expires, 0)),
			Issuer:    "permission",
		},
	}
	validToken, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte("test-secret"))
	testToken.AccessToken = validToken

	tests := []struct {
		name      string
		req       entity.ValidationTokenReq
		mockSetup func(*mockRepo.MockTokenRepository)
		wantErr   bool
	}{
		{
			name: "成功驗證 Token",
			req: entity.ValidationTokenReq{
				Token: validToken,
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokenByID(ctx, "test-id").
					Return(testToken, nil)
			},
			wantErr: false,
		},
		{
			name: "驗證失敗 - Token 無效",
			req: entity.ValidationTokenReq{
				Token: "invalid-token",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				// parseClaims will fail, no repo call
			},
			wantErr: true,
		},
		{
			name: "驗證失敗 - Token 不存在",
			req: entity.ValidationTokenReq{
				Token: validToken,
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokenByID(ctx, "test-id").
					Return(entity.Token{}, errors.New("token not found"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockTokenRepo := mockRepo.NewMockTokenRepository(mockCtrl)
			tt.mockSetup(mockTokenRepo)

			uc := MustTokenUseCase(TokenUseCaseParam{
				TokenRepo: mockTokenRepo,
				Config: &config.Config{
					Token: struct {
						AccessSecret       string
						RefreshSecret      string
						AccessTokenExpiry  time.Duration
						RefreshTokenExpiry time.Duration
						OneTimeTokenExpiry time.Duration
						MaxTokensPerUser   int
						MaxTokensPerDevice int
					}{
						AccessSecret:       "test-secret",
						AccessTokenExpiry:  time.Hour,
						RefreshTokenExpiry: time.Hour * 24,
						RefreshSecret:      "refresh-secret",
						OneTimeTokenExpiry: time.Minute * 5,
						MaxTokensPerUser:   10,
						MaxTokensPerDevice: 5,
					},
				},
			})

			resp, err := uc.ValidationToken(ctx, tt.req)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, testToken.ID, resp.Token.ID)
				assert.Equal(t, testToken.UID, resp.Token.UID)
				assert.NotNil(t, resp.Data)
			}
		})
	}
}

func TestTokenUseCase_CancelToken(t *testing.T) {
	ctx := context.Background()
	now := time.Now().UTC()
	expires := now.Add(time.Hour).Unix()

	testToken := entity.Token{
		ID:               "test-id",
		UID:              "user123",
		DeviceID:         "device123",
		ExpiresIn:        int(expires),
		RefreshExpiresIn: int(now.Add(time.Hour * 24).Unix()),
		AccessCreateAt:   now,
		RefreshCreateAt:  now,
	}

	claims := entity.Claims{
		Data: map[string]string{
			"id":      testToken.ID,
			"uid":     "user123",
			"role":    "admin",
			"scope":   "read",
			"account": "user123",
		},
		RegisteredClaims: jwt.RegisteredClaims{
			ID:        testToken.ID,
			ExpiresAt: jwt.NewNumericDate(time.Unix(expires, 0)),
			Issuer:    "permission",
		},
	}
	validToken, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte("test-secret"))
	testToken.AccessToken = validToken

	tests := []struct {
		name      string
		req       entity.CancelTokenReq
		mockSetup func(*mockRepo.MockTokenRepository)
		wantErr   bool
	}{
		{
			name: "成功取消 Token",
			req: entity.CancelTokenReq{
				Token: validToken,
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokenByID(ctx, "test-id").
					Return(testToken, nil)
				mockTokenRepo.EXPECT().Delete(ctx, testToken).Return(nil)
			},
			wantErr: false,
		},
		{
			name: "取消失敗 - Token 無效",
			req: entity.CancelTokenReq{
				Token: "invalid-token",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				// parseClaims will fail, no repo call
			},
			wantErr: true,
		},
		{
			name: "取消失敗 - Token 不存在",
			req: entity.CancelTokenReq{
				Token: validToken,
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokenByID(ctx, "test-id").
					Return(entity.Token{}, errors.New("token not found"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockTokenRepo := mockRepo.NewMockTokenRepository(mockCtrl)
			tt.mockSetup(mockTokenRepo)

			uc := MustTokenUseCase(TokenUseCaseParam{
				TokenRepo: mockTokenRepo,
				Config: &config.Config{
					Token: struct {
						AccessSecret       string
						RefreshSecret      string
						AccessTokenExpiry  time.Duration
						RefreshTokenExpiry time.Duration
						OneTimeTokenExpiry time.Duration
						MaxTokensPerUser   int
						MaxTokensPerDevice int
					}{
						AccessSecret:       "test-secret",
						AccessTokenExpiry:  time.Hour,
						RefreshTokenExpiry: time.Hour * 24,
						RefreshSecret:      "refresh-secret",
						OneTimeTokenExpiry: time.Minute * 5,
						MaxTokensPerUser:   10,
						MaxTokensPerDevice: 5,
					},
				},
			})

			err := uc.CancelToken(ctx, tt.req)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestTokenUseCase_CancelTokens(t *testing.T) {
	ctx := context.Background()

	tests := []struct {
		name      string
		req       entity.DoTokenByUIDReq
		mockSetup func(*mockRepo.MockTokenRepository)
		wantErr   bool
	}{
		{
			name: "成功取消 UID 的所有 Token",
			req: entity.DoTokenByUIDReq{
				UID: "user123",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					DeleteAccessTokensByUID(ctx, "user123").
					Return(nil)
			},
			wantErr: false,
		},
		{
			name: "成功取消指定 ID 的 Token",
			req: entity.DoTokenByUIDReq{
				IDs: []string{"token1", "token2"},
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					DeleteAccessTokenByID(ctx, []string{"token1", "token2"}).
					Return(nil)
			},
			wantErr: false,
		},
		{
			name: "成功取消 UID 和 ID 的 Token",
			req: entity.DoTokenByUIDReq{
				UID: "user123",
				IDs: []string{"token1"},
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					DeleteAccessTokensByUID(ctx, "user123").
					Return(nil)
				mockTokenRepo.EXPECT().
					DeleteAccessTokenByID(ctx, []string{"token1"}).
					Return(nil)
			},
			wantErr: false,
		},
		{
			name: "取消失敗 - UID 刪除錯誤",
			req: entity.DoTokenByUIDReq{
				UID: "user123",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					DeleteAccessTokensByUID(ctx, "user123").
					Return(errors.New("delete error"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockTokenRepo := mockRepo.NewMockTokenRepository(mockCtrl)
			tt.mockSetup(mockTokenRepo)

			uc := MustTokenUseCase(TokenUseCaseParam{
				TokenRepo: mockTokenRepo,
				Config: &config.Config{
					Token: struct {
						AccessSecret       string
						RefreshSecret      string
						AccessTokenExpiry  time.Duration
						RefreshTokenExpiry time.Duration
						OneTimeTokenExpiry time.Duration
						MaxTokensPerUser   int
						MaxTokensPerDevice int
					}{
						AccessSecret:       "test-secret",
						AccessTokenExpiry:  time.Hour,
						RefreshTokenExpiry: time.Hour * 24,
						RefreshSecret:      "refresh-secret",
						OneTimeTokenExpiry: time.Minute * 5,
						MaxTokensPerUser:   10,
						MaxTokensPerDevice: 5,
					},
				},
			})

			err := uc.CancelTokens(ctx, tt.req)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestTokenUseCase_CancelTokenByDeviceID(t *testing.T) {
	ctx := context.Background()

	tests := []struct {
		name      string
		req       entity.DoTokenByDeviceIDReq
		mockSetup func(*mockRepo.MockTokenRepository)
		wantErr   bool
	}{
		{
			name: "成功取消 DeviceID 的所有 Token",
			req: entity.DoTokenByDeviceIDReq{
				DeviceID: "device123",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					DeleteAccessTokensByDeviceID(ctx, "device123").
					Return(nil)
			},
			wantErr: false,
		},
		{
			name: "取消失敗 - 刪除錯誤",
			req: entity.DoTokenByDeviceIDReq{
				DeviceID: "device123",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					DeleteAccessTokensByDeviceID(ctx, "device123").
					Return(errors.New("delete error"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockTokenRepo := mockRepo.NewMockTokenRepository(mockCtrl)
			tt.mockSetup(mockTokenRepo)

			uc := MustTokenUseCase(TokenUseCaseParam{
				TokenRepo: mockTokenRepo,
				Config: &config.Config{
					Token: struct {
						AccessSecret       string
						RefreshSecret      string
						AccessTokenExpiry  time.Duration
						RefreshTokenExpiry time.Duration
						OneTimeTokenExpiry time.Duration
						MaxTokensPerUser   int
						MaxTokensPerDevice int
					}{
						AccessSecret:       "test-secret",
						AccessTokenExpiry:  time.Hour,
						RefreshTokenExpiry: time.Hour * 24,
						RefreshSecret:      "refresh-secret",
						OneTimeTokenExpiry: time.Minute * 5,
						MaxTokensPerUser:   10,
						MaxTokensPerDevice: 5,
					},
				},
			})

			err := uc.CancelTokenByDeviceID(ctx, tt.req)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestTokenUseCase_GetUserTokensByUID(t *testing.T) {
	ctx := context.Background()
	now := time.Now().UTC()

	testTokens := []entity.Token{
		{
			ID:               "token1",
			UID:              "user123",
			AccessToken:      "access1",
			RefreshToken:     "refresh1",
			ExpiresIn:        int(now.Add(time.Hour).Unix()),
			RefreshExpiresIn: int(now.Add(time.Hour * 24).Unix()),
		},
		{
			ID:               "token2",
			UID:              "user123",
			AccessToken:      "access2",
			RefreshToken:     "refresh2",
			ExpiresIn:        int(now.Add(time.Hour).Unix()),
			RefreshExpiresIn: int(now.Add(time.Hour * 24).Unix()),
		},
	}

	tests := []struct {
		name      string
		req       entity.QueryTokenByUIDReq
		mockSetup func(*mockRepo.MockTokenRepository)
		wantCount int
		wantErr   bool
	}{
		{
			name: "成功獲取 UID 的所有 Token",
			req: entity.QueryTokenByUIDReq{
				UID: "user123",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokensByUID(ctx, "user123").
					Return(testTokens, nil)
			},
			wantCount: 2,
			wantErr:   false,
		},
		{
			name: "成功獲取但沒有 Token",
			req: entity.QueryTokenByUIDReq{
				UID: "user999",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokensByUID(ctx, "user999").
					Return([]entity.Token{}, nil)
			},
			wantCount: 0,
			wantErr:   false,
		},
		{
			name: "獲取失敗 - Repository 錯誤",
			req: entity.QueryTokenByUIDReq{
				UID: "user123",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokensByUID(ctx, "user123").
					Return(nil, errors.New("db error"))
			},
			wantCount: 0,
			wantErr:   true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockTokenRepo := mockRepo.NewMockTokenRepository(mockCtrl)
			tt.mockSetup(mockTokenRepo)

			uc := MustTokenUseCase(TokenUseCaseParam{
				TokenRepo: mockTokenRepo,
				Config: &config.Config{
					Token: struct {
						AccessSecret       string
						RefreshSecret      string
						AccessTokenExpiry  time.Duration
						RefreshTokenExpiry time.Duration
						OneTimeTokenExpiry time.Duration
						MaxTokensPerUser   int
						MaxTokensPerDevice int
					}{
						AccessSecret:       "test-secret",
						AccessTokenExpiry:  time.Hour,
						RefreshTokenExpiry: time.Hour * 24,
						RefreshSecret:      "refresh-secret",
						OneTimeTokenExpiry: time.Minute * 5,
						MaxTokensPerUser:   10,
						MaxTokensPerDevice: 5,
					},
				},
			})

			resp, err := uc.GetUserTokensByUID(ctx, tt.req)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Len(t, resp, tt.wantCount)
			}
		})
	}
}

func TestTokenUseCase_GetUserTokensByDeviceID(t *testing.T) {
	ctx := context.Background()
	now := time.Now().UTC()

	testTokens := []entity.Token{
		{
			ID:               "token1",
			UID:              "user123",
			DeviceID:         "device123",
			AccessToken:      "access1",
			RefreshToken:     "refresh1",
			ExpiresIn:        int(now.Add(time.Hour).Unix()),
			RefreshExpiresIn: int(now.Add(time.Hour * 24).Unix()),
		},
	}

	tests := []struct {
		name      string
		req       entity.DoTokenByDeviceIDReq
		mockSetup func(*mockRepo.MockTokenRepository)
		wantCount int
		wantErr   bool
	}{
		{
			name: "成功獲取 DeviceID 的所有 Token",
			req: entity.DoTokenByDeviceIDReq{
				DeviceID: "device123",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokensByDeviceID(ctx, "device123").
					Return(testTokens, nil)
			},
			wantCount: 1,
			wantErr:   false,
		},
		{
			name: "獲取失敗 - Repository 錯誤",
			req: entity.DoTokenByDeviceIDReq{
				DeviceID: "device123",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokensByDeviceID(ctx, "device123").
					Return(nil, errors.New("db error"))
			},
			wantCount: 0,
			wantErr:   true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockTokenRepo := mockRepo.NewMockTokenRepository(mockCtrl)
			tt.mockSetup(mockTokenRepo)

			uc := MustTokenUseCase(TokenUseCaseParam{
				TokenRepo: mockTokenRepo,
				Config: &config.Config{
					Token: struct {
						AccessSecret       string
						RefreshSecret      string
						AccessTokenExpiry  time.Duration
						RefreshTokenExpiry time.Duration
						OneTimeTokenExpiry time.Duration
						MaxTokensPerUser   int
						MaxTokensPerDevice int
					}{
						AccessSecret:       "test-secret",
						AccessTokenExpiry:  time.Hour,
						RefreshTokenExpiry: time.Hour * 24,
						RefreshSecret:      "refresh-secret",
						OneTimeTokenExpiry: time.Minute * 5,
						MaxTokensPerUser:   10,
						MaxTokensPerDevice: 5,
					},
				},
			})

			resp, err := uc.GetUserTokensByDeviceID(ctx, tt.req)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Len(t, resp, tt.wantCount)
			}
		})
	}
}

func TestTokenUseCase_NewOneTimeToken(t *testing.T) {
	ctx := context.Background()
	now := time.Now().UTC()
	expires := now.Add(time.Hour).Unix()

	testToken := entity.Token{
		ID:               "test-id",
		UID:              "user123",
		DeviceID:         "device123",
		ExpiresIn:        int(expires),
		RefreshExpiresIn: int(now.Add(time.Hour * 24).Unix()),
		AccessCreateAt:   now,
		RefreshCreateAt:  now,
	}

	claims := entity.Claims{
		Data: map[string]string{
			"id":      testToken.ID,
			"uid":     "user123",
			"role":    "admin",
			"scope":   "read",
			"account": "user123",
		},
		RegisteredClaims: jwt.RegisteredClaims{
			ID:        testToken.ID,
			ExpiresAt: jwt.NewNumericDate(time.Unix(expires, 0)),
			Issuer:    "permission",
		},
	}
	validToken, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte("test-secret"))
	testToken.AccessToken = validToken

	tests := []struct {
		name      string
		req       entity.CreateOneTimeTokenReq
		mockSetup func(*mockRepo.MockTokenRepository)
		wantErr   bool
	}{
		{
			name: "成功創建 OneTimeToken",
			req: entity.CreateOneTimeTokenReq{
				Token: validToken,
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokenByID(ctx, "test-id").
					Return(testToken, nil)
				mockTokenRepo.EXPECT().
					CreateOneTimeToken(ctx, gomock.Any(), gomock.Any(), time.Minute).
					Return(nil)
			},
			wantErr: false,
		},
		{
			name: "創建失敗 - Token 無效",
			req: entity.CreateOneTimeTokenReq{
				Token: "invalid-token",
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				// parseClaims will fail, no repo call
			},
			wantErr: true,
		},
		{
			name: "創建失敗 - Token 不存在",
			req: entity.CreateOneTimeTokenReq{
				Token: validToken,
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokenByID(ctx, "test-id").
					Return(entity.Token{}, errors.New("token not found"))
			},
			wantErr: true,
		},
		{
			name: "創建失敗 - Repository 錯誤",
			req: entity.CreateOneTimeTokenReq{
				Token: validToken,
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokenByID(ctx, "test-id").
					Return(testToken, nil)
				mockTokenRepo.EXPECT().
					CreateOneTimeToken(ctx, gomock.Any(), gomock.Any(), time.Minute).
					Return(errors.New("create error"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockTokenRepo := mockRepo.NewMockTokenRepository(mockCtrl)
			tt.mockSetup(mockTokenRepo)

			uc := MustTokenUseCase(TokenUseCaseParam{
				TokenRepo: mockTokenRepo,
				Config: &config.Config{
					Token: struct {
						AccessSecret       string
						RefreshSecret      string
						AccessTokenExpiry  time.Duration
						RefreshTokenExpiry time.Duration
						OneTimeTokenExpiry time.Duration
						MaxTokensPerUser   int
						MaxTokensPerDevice int
					}{
						AccessSecret:       "test-secret",
						AccessTokenExpiry:  time.Hour,
						RefreshTokenExpiry: time.Hour * 24,
						RefreshSecret:      "refresh-secret",
						OneTimeTokenExpiry: time.Minute * 5,
						MaxTokensPerUser:   10,
						MaxTokensPerDevice: 5,
					},
				},
			})

			resp, err := uc.NewOneTimeToken(ctx, tt.req)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.NotEmpty(t, resp.OneTimeToken)
			}
		})
	}
}

func TestTokenUseCase_CancelOneTimeToken(t *testing.T) {
	ctx := context.Background()

	tests := []struct {
		name      string
		req       entity.CancelOneTimeTokenReq
		mockSetup func(*mockRepo.MockTokenRepository)
		wantErr   bool
	}{
		{
			name: "成功取消 OneTimeToken",
			req: entity.CancelOneTimeTokenReq{
				Token: []string{"one-time-token"},
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					DeleteOneTimeToken(ctx, []string{"one-time-token"}, nil).
					Return(nil)
			},
			wantErr: false,
		},
		{
			name: "取消失敗 - Repository 錯誤",
			req: entity.CancelOneTimeTokenReq{
				Token: []string{"one-time-token"},
			},
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					DeleteOneTimeToken(ctx, []string{"one-time-token"}, nil).
					Return(errors.New("delete error"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockTokenRepo := mockRepo.NewMockTokenRepository(mockCtrl)
			tt.mockSetup(mockTokenRepo)

			uc := MustTokenUseCase(TokenUseCaseParam{
				TokenRepo: mockTokenRepo,
				Config: &config.Config{
					Token: struct {
						AccessSecret       string
						RefreshSecret      string
						AccessTokenExpiry  time.Duration
						RefreshTokenExpiry time.Duration
						OneTimeTokenExpiry time.Duration
						MaxTokensPerUser   int
						MaxTokensPerDevice int
					}{
						AccessSecret:       "test-secret",
						AccessTokenExpiry:  time.Hour,
						RefreshTokenExpiry: time.Hour * 24,
						RefreshSecret:      "refresh-secret",
						OneTimeTokenExpiry: time.Minute * 5,
						MaxTokensPerUser:   10,
						MaxTokensPerDevice: 5,
					},
				},
			})

			err := uc.CancelOneTimeToken(ctx, tt.req)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestTokenUseCase_ReadTokenBasicData(t *testing.T) {
	ctx := context.Background()
	now := time.Now().UTC()
	expires := now.Add(time.Hour).Unix()

	claims := entity.Claims{
		Data: map[string]string{
			"uid":     "user123",
			"role":    "admin",
			"scope":   "read",
			"account": "user123",
		},
		RegisteredClaims: jwt.RegisteredClaims{
			ID:        "test-id",
			ExpiresAt: jwt.NewNumericDate(time.Unix(expires, 0)),
			Issuer:    "permission",
		},
	}
	validToken, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte("test-secret"))

	tests := []struct {
		name      string
		token     string
		mockSetup func(*mockRepo.MockTokenRepository)
		wantErr   bool
		checkData func(*testing.T, map[string]string)
	}{
		{
			name:  "成功讀取 Token 數據",
			token: validToken,
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				// No repo call needed for ReadTokenBasicData
			},
			wantErr: false,
			checkData: func(t *testing.T, data map[string]string) {
				assert.Equal(t, "user123", data["uid"])
				assert.Equal(t, "admin", data["role"])
				assert.Equal(t, "read", data["scope"])
			},
		},
		{
			name:  "讀取失敗 - Token 無效",
			token: "invalid-token",
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				// No repo call
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockTokenRepo := mockRepo.NewMockTokenRepository(mockCtrl)
			tt.mockSetup(mockTokenRepo)

			uc := MustTokenUseCase(TokenUseCaseParam{
				TokenRepo: mockTokenRepo,
				Config: &config.Config{
					Token: struct {
						AccessSecret       string
						RefreshSecret      string
						AccessTokenExpiry  time.Duration
						RefreshTokenExpiry time.Duration
						OneTimeTokenExpiry time.Duration
						MaxTokensPerUser   int
						MaxTokensPerDevice int
					}{
						AccessSecret:       "test-secret",
						AccessTokenExpiry:  time.Hour,
						RefreshTokenExpiry: time.Hour * 24,
						RefreshSecret:      "refresh-secret",
						OneTimeTokenExpiry: time.Minute * 5,
						MaxTokensPerUser:   10,
						MaxTokensPerDevice: 5,
					},
				},
			})

			data, err := uc.ReadTokenBasicData(ctx, tt.token)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, data)
				if tt.checkData != nil {
					tt.checkData(t, data)
				}
			}
		})
	}
}

func TestTokenUseCase_BlacklistToken(t *testing.T) {
	ctx := context.Background()
	now := time.Now().UTC()
	expires := now.Add(time.Hour).Unix()

	claims := entity.Claims{
		Data: map[string]string{
			"uid":     "user123",
			"role":    "admin",
			"scope":   "read",
			"account": "user123",
		},
		RegisteredClaims: jwt.RegisteredClaims{
			ID:        "test-jti",
			ExpiresAt: jwt.NewNumericDate(time.Unix(expires, 0)),
			Issuer:    "permission",
		},
	}
	validToken, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte("test-secret"))

	tests := []struct {
		name      string
		token     string
		reason    string
		mockSetup func(*mockRepo.MockTokenRepository)
		wantErr   bool
	}{
		{
			name:   "成功將 Token 加入黑名單",
			token:  validToken,
			reason: "user logout",
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					AddToBlacklist(ctx, gomock.Any(), time.Duration(0)).
					Return(nil)
			},
			wantErr: false,
		},
		{
			name:   "加入黑名單失敗 - Token 無效",
			token:  "invalid-token",
			reason: "test",
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				// parseToken will fail, no repo call
			},
			wantErr: true,
		},
		{
			name:   "加入黑名單失敗 - Repository 錯誤",
			token:  validToken,
			reason: "test",
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					AddToBlacklist(ctx, gomock.Any(), time.Duration(0)).
					Return(errors.New("redis error"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockTokenRepo := mockRepo.NewMockTokenRepository(mockCtrl)
			tt.mockSetup(mockTokenRepo)

			uc := MustTokenUseCase(TokenUseCaseParam{
				TokenRepo: mockTokenRepo,
				Config: &config.Config{
					Token: struct {
						AccessSecret       string
						RefreshSecret      string
						AccessTokenExpiry  time.Duration
						RefreshTokenExpiry time.Duration
						OneTimeTokenExpiry time.Duration
						MaxTokensPerUser   int
						MaxTokensPerDevice int
					}{
						AccessSecret:       "test-secret",
						AccessTokenExpiry:  time.Hour,
						RefreshTokenExpiry: time.Hour * 24,
						RefreshSecret:      "refresh-secret",
						OneTimeTokenExpiry: time.Minute * 5,
						MaxTokensPerUser:   10,
						MaxTokensPerDevice: 5,
					},
				},
			})

			err := uc.BlacklistToken(ctx, tt.token, tt.reason)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestTokenUseCase_IsTokenBlacklisted(t *testing.T) {
	ctx := context.Background()

	tests := []struct {
		name            string
		jti             string
		mockSetup       func(*mockRepo.MockTokenRepository)
		wantBlacklisted bool
		wantErr         bool
	}{
		{
			name: "Token 在黑名單中",
			jti:  "test-jti",
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					IsBlacklisted(ctx, "test-jti").
					Return(true, nil)
			},
			wantBlacklisted: true,
			wantErr:         false,
		},
		{
			name: "Token 不在黑名單中",
			jti:  "test-jti",
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					IsBlacklisted(ctx, "test-jti").
					Return(false, nil)
			},
			wantBlacklisted: false,
			wantErr:         false,
		},
		{
			name: "檢查失敗 - Repository 錯誤",
			jti:  "test-jti",
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					IsBlacklisted(ctx, "test-jti").
					Return(false, errors.New("redis error"))
			},
			wantBlacklisted: false,
			wantErr:         true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockTokenRepo := mockRepo.NewMockTokenRepository(mockCtrl)
			tt.mockSetup(mockTokenRepo)

			uc := MustTokenUseCase(TokenUseCaseParam{
				TokenRepo: mockTokenRepo,
				Config: &config.Config{
					Token: struct {
						AccessSecret       string
						RefreshSecret      string
						AccessTokenExpiry  time.Duration
						RefreshTokenExpiry time.Duration
						OneTimeTokenExpiry time.Duration
						MaxTokensPerUser   int
						MaxTokensPerDevice int
					}{
						AccessSecret:       "test-secret",
						AccessTokenExpiry:  time.Hour,
						RefreshTokenExpiry: time.Hour * 24,
						RefreshSecret:      "refresh-secret",
						OneTimeTokenExpiry: time.Minute * 5,
						MaxTokensPerUser:   10,
						MaxTokensPerDevice: 5,
					},
				},
			})

			isBlacklisted, err := uc.IsTokenBlacklisted(ctx, tt.jti)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.wantBlacklisted, isBlacklisted)
			}
		})
	}
}

func TestTokenUseCase_BlacklistAllUserTokens(t *testing.T) {
	ctx := context.Background()
	now := time.Now().UTC()
	expires := now.Add(time.Hour).Unix()

	// 創建測試用的 tokens
	testToken1 := entity.Token{
		ID:               "token1",
		UID:              "user123",
		ExpiresIn:        int(expires),
		RefreshExpiresIn: int(now.Add(time.Hour * 24).Unix()),
	}

	testToken2 := entity.Token{
		ID:               "token2",
		UID:              "user123",
		ExpiresIn:        int(expires),
		RefreshExpiresIn: int(now.Add(time.Hour * 24).Unix()),
	}

	// 為每個 token 創建 JWT
	claims1 := entity.Claims{
		Data: map[string]string{
			"uid": "user123",
			"jti": "jti1",
			"exp": fmt.Sprintf("%d", expires),
		},
		RegisteredClaims: jwt.RegisteredClaims{
			ID:        "jti1",
			ExpiresAt: jwt.NewNumericDate(time.Unix(expires, 0)),
			Issuer:    "permission",
		},
	}
	token1JWT, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims1).SignedString([]byte("test-secret"))
	testToken1.AccessToken = token1JWT

	claims2 := entity.Claims{
		Data: map[string]string{
			"uid": "user123",
			"jti": "jti2",
			"exp": fmt.Sprintf("%d", expires),
		},
		RegisteredClaims: jwt.RegisteredClaims{
			ID:        "jti2",
			ExpiresAt: jwt.NewNumericDate(time.Unix(expires, 0)),
			Issuer:    "permission",
		},
	}
	token2JWT, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims2).SignedString([]byte("test-secret"))
	testToken2.AccessToken = token2JWT

	tests := []struct {
		name      string
		uid       string
		reason    string
		mockSetup func(*mockRepo.MockTokenRepository)
		wantErr   bool
	}{
		{
			name:   "成功將用戶所有 Token 加入黑名單",
			uid:    "user123",
			reason: "security issue",
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokensByUID(ctx, "user123").
					Return([]entity.Token{testToken1, testToken2}, nil)
				mockTokenRepo.EXPECT().
					AddToBlacklist(ctx, gomock.Any(), time.Duration(0)).
					Return(nil).
					Times(2)
				mockTokenRepo.EXPECT().
					DeleteAccessTokensByUID(ctx, "user123").
					Return(nil)
			},
			wantErr: false,
		},
		{
			name:   "成功但用戶沒有 Token",
			uid:    "user999",
			reason: "test",
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokensByUID(ctx, "user999").
					Return([]entity.Token{}, nil)
				mockTokenRepo.EXPECT().
					DeleteAccessTokensByUID(ctx, "user999").
					Return(nil)
			},
			wantErr: false,
		},
		{
			name:   "失敗 - 獲取用戶 Token 失敗",
			uid:    "user123",
			reason: "test",
			mockSetup: func(mockTokenRepo *mockRepo.MockTokenRepository) {
				mockTokenRepo.EXPECT().
					GetAccessTokensByUID(ctx, "user123").
					Return(nil, errors.New("db error"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockTokenRepo := mockRepo.NewMockTokenRepository(mockCtrl)
			tt.mockSetup(mockTokenRepo)

			uc := MustTokenUseCase(TokenUseCaseParam{
				TokenRepo: mockTokenRepo,
				Config: &config.Config{
					Token: struct {
						AccessSecret       string
						RefreshSecret      string
						AccessTokenExpiry  time.Duration
						RefreshTokenExpiry time.Duration
						OneTimeTokenExpiry time.Duration
						MaxTokensPerUser   int
						MaxTokensPerDevice int
					}{
						AccessSecret:       "test-secret",
						AccessTokenExpiry:  time.Hour,
						RefreshTokenExpiry: time.Hour * 24,
						RefreshSecret:      "refresh-secret",
						OneTimeTokenExpiry: time.Minute * 5,
						MaxTokensPerUser:   10,
						MaxTokensPerDevice: 5,
					},
				},
			})

			err := uc.BlacklistAllUserTokens(ctx, tt.uid, tt.reason)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}
