package game_test

import (
	"bindbox-game/internal/repository/mysql"
	"bindbox-game/internal/service/game"
	"context"
	"fmt"
	"testing"
	"time"

	"bindbox-game/internal/pkg/logger"

	"github.com/alicebob/miniredis/v2"
	"github.com/redis/go-redis/v9"
	"github.com/stretchr/testify/assert"
	"go.uber.org/zap"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// Mock logger
type MockLogger struct {
	logger.CustomLogger
}

func (l *MockLogger) Info(msg string, fields ...zap.Field)  {}
func (l *MockLogger) Error(msg string, fields ...zap.Field) {}
func (l *MockLogger) Warn(msg string, fields ...zap.Field)  {}
func (l *MockLogger) Debug(msg string, fields ...zap.Field) {}

func TestGenerateToken_FreeMode(t *testing.T) {
	// 1. Setup Miniredis
	mr, err := miniredis.Run()
	assert.NoError(t, err)
	defer mr.Close()

	rdb := redis.NewClient(&redis.Options{
		Addr: mr.Addr(),
	})

	// 2. Setup GORM (SQLite in-memory)
	// We use an empty DB to ensure NO ticket exists
	db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
	assert.NoError(t, err)

	// AutoMigrate to make sure table exists (even if empty) to avoid SQL errors
	// err = db.AutoMigrate(&model.UserGameTickets{})
	// assert.NoError(t, err)

	repo := mysql.NewTestRepo(db)

	// 3. Create Service
	svc := game.NewGameTokenService(&MockLogger{}, repo, rdb)

	// 4. Test Case: minesweeper_free
	ctx := context.Background()
	userID := int64(12345)
	username := "testuser"
	gameCode := "minesweeper_free"

	// Should succeed even though DB is empty (bypasses ticket check)
	token, ticket, expiresAt, err := svc.GenerateToken(ctx, userID, username, "", gameCode)

	assert.NoError(t, err)
	assert.NotEmpty(t, token)
	assert.NotEmpty(t, ticket)
	assert.True(t, expiresAt.After(time.Now()))

	// 5. Verify Redis Key Format
	// Expected: "userID:gameCode"
	ticketKey := fmt.Sprintf("game:token:ticket:%s", ticket)
	val, err := rdb.Get(ctx, ticketKey).Result()
	assert.NoError(t, err)
	assert.Equal(t, fmt.Sprintf("%d:%s", userID, gameCode), val)

	// 6. Test Validation
	claims, err := svc.ValidateToken(ctx, token)
	assert.NoError(t, err)
	assert.Equal(t, userID, claims.UserID)
	assert.Equal(t, gameCode, claims.GameType)
}

func TestGenerateToken_PaidMode_NoTicket(t *testing.T) {
	// 1. Setup
	mr, err := miniredis.Run()
	assert.NoError(t, err)
	defer mr.Close()
	rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
	db, _ := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
	// db.AutoMigrate(&model.UserGameTickets{}) // Empty table
	repo := mysql.NewTestRepo(db)
	svc := game.NewGameTokenService(&MockLogger{}, repo, rdb)

	// 2. Test Case: normal game
	// Should FAIL because no ticket in DB
	_, _, _, err = svc.GenerateToken(context.Background(), 12345, "user", "", "minesweeper_paid")

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "no available game tickets")
}

func TestValidateToken_LegacyFormat_ShouldFail(t *testing.T) {
	// Test that strict mode rejects legacy keys
	mr, _ := miniredis.Run()
	defer mr.Close()
	rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})

	svc := game.NewGameTokenService(&MockLogger{}, mysql.NewTestRepo(nil), rdb)
	userID := int64(999)

	// Generate valid token first
	token, ticket, _, _ := svc.GenerateToken(context.Background(), userID, "user", "", "minesweeper_free")

	// Overwrite Redis with legacy format (just userID)
	rdb.Set(context.Background(), "game:token:ticket:"+ticket, fmt.Sprintf("%d", userID), time.Hour)

	// Now Validate - SHOULD FAIL
	_, err := svc.ValidateToken(context.Background(), token)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid ticket format")
}
