package user

import (
	"context"
	"os"
	"testing"
	"time"

	"dance-lessons-coach/pkg/config"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// createTestConfig creates a test configuration with telemetry disabled
func createTestConfig() *config.Config {
	return &config.Config{
		Telemetry: config.TelemetryConfig{
			Enabled: false,
			Persistence: config.PersistenceTelemetryConfig{
				Enabled: false,
			},
		},
	}
}

func TestSQLiteRepository(t *testing.T) {
	t.Run("CRUD operations", func(t *testing.T) {
		// Create a temporary database
		dbPath := "test_db.sqlite"
		defer os.Remove(dbPath)

		cfg := createTestConfig()
		repo, err := NewSQLiteRepository(dbPath, cfg)
		require.NoError(t, err)
		defer repo.Close()

		ctx := context.Background()

		// Test CreateUser
		user := &User{
			Username:     "testuser",
			PasswordHash: "hashedpassword",
			Description:  ptrString("Test user"),
			CurrentGoal:  ptrString("Learn to dance"),
			IsAdmin:      false,
		}

		err = repo.CreateUser(ctx, user)
		require.NoError(t, err)
		assert.NotZero(t, user.ID)

		// Test GetUserByUsername
		retrievedUser, err := repo.GetUserByUsername(ctx, "testuser")
		require.NoError(t, err)
		assert.NotNil(t, retrievedUser)
		assert.Equal(t, "testuser", retrievedUser.Username)

		// Test UserExists
		exists, err := repo.UserExists(ctx, "testuser")
		require.NoError(t, err)
		assert.True(t, exists)

		// Test UpdateUser
		retrievedUser.Description = ptrString("Updated description")
		err = repo.UpdateUser(ctx, retrievedUser)
		require.NoError(t, err)

		// Verify update
		updatedUser, err := repo.GetUserByUsername(ctx, "testuser")
		require.NoError(t, err)
		assert.Equal(t, "Updated description", *updatedUser.Description)

		// Test AllowPasswordReset
		err = repo.AllowPasswordReset(ctx, "testuser")
		require.NoError(t, err)

		// Verify password reset flag
		userWithReset, err := repo.GetUserByUsername(ctx, "testuser")
		require.NoError(t, err)
		assert.True(t, userWithReset.AllowPasswordReset)

		// Test CompletePasswordReset
		err = repo.CompletePasswordReset(ctx, "testuser", "newhashedpassword")
		require.NoError(t, err)

		// Verify password reset completion
		userAfterReset, err := repo.GetUserByUsername(ctx, "testuser")
		require.NoError(t, err)
		assert.Equal(t, "newhashedpassword", userAfterReset.PasswordHash)
		assert.False(t, userAfterReset.AllowPasswordReset)

		// Test DeleteUser
		err = repo.DeleteUser(ctx, userAfterReset.ID)
		require.NoError(t, err)

		// Verify deletion
		deletedUser, err := repo.GetUserByUsername(ctx, "testuser")
		require.NoError(t, err)
		assert.Nil(t, deletedUser)
	})
}

func TestAuthService(t *testing.T) {
	t.Run("Password hashing and authentication", func(t *testing.T) {
		// Create a temporary database
		dbPath := "test_auth_db.sqlite"
		defer os.Remove(dbPath)

		cfg := createTestConfig()
		repo, err := NewSQLiteRepository(dbPath, cfg)
		require.NoError(t, err)
		defer repo.Close()

		ctx := context.Background()

		// Create user service
		jwtConfig := JWTConfig{
			Secret:         "test-secret",
			ExpirationTime: time.Hour,
			Issuer:         "test-issuer",
		}
		userService := NewUserService(repo, jwtConfig, "admin123")

		// Test password hashing
		password := "testpassword123"
		hashedPassword, err := userService.HashPassword(ctx, password)
		require.NoError(t, err)
		assert.NotEmpty(t, hashedPassword)

		// Create a test user
		user := &User{
			Username:     "testuser",
			PasswordHash: hashedPassword,
		}
		err = repo.CreateUser(ctx, user)
		require.NoError(t, err)

		// Test successful authentication
		authenticatedUser, err := userService.Authenticate(ctx, "testuser", password)
		require.NoError(t, err)
		assert.NotNil(t, authenticatedUser)
		assert.Equal(t, "testuser", authenticatedUser.Username)

		// Test failed authentication with wrong password
		_, err = userService.Authenticate(ctx, "testuser", "wrongpassword")
		assert.Error(t, err)
		assert.Equal(t, "invalid credentials", err.Error())

		// Test JWT generation
		token, err := userService.GenerateJWT(ctx, authenticatedUser)
		require.NoError(t, err)
		assert.NotEmpty(t, token)

		// Test JWT validation
		validatedUser, err := userService.ValidateJWT(ctx, token)
		require.NoError(t, err)
		assert.NotNil(t, validatedUser)
		assert.Equal(t, authenticatedUser.ID, validatedUser.ID)

		// Test admin authentication
		adminUser, err := userService.AdminAuthenticate(ctx, "admin123")
		require.NoError(t, err)
		assert.NotNil(t, adminUser)
		assert.True(t, adminUser.IsAdmin)
		assert.Equal(t, "admin", adminUser.Username)

		// Test failed admin authentication
		_, err = userService.AdminAuthenticate(ctx, "wrongadminpassword")
		assert.Error(t, err)
		assert.Equal(t, "invalid admin credentials", err.Error())
	})
}

func TestPasswordResetService(t *testing.T) {
	t.Run("Password reset workflow", func(t *testing.T) {
		// Create a temporary database
		dbPath := "test_reset_db.sqlite"
		defer os.Remove(dbPath)

		cfg := createTestConfig()
		repo, err := NewSQLiteRepository(dbPath, cfg)
		require.NoError(t, err)
		defer repo.Close()

		ctx := context.Background()

		// Create user service
		jwtConfig := JWTConfig{
			Secret:         "test-secret",
			ExpirationTime: time.Hour,
			Issuer:         "test-issuer",
		}
		userService := NewUserService(repo, jwtConfig, "admin123")

		// Create a test user
		password := "oldpassword123"
		hashedPassword, err := userService.HashPassword(ctx, password)
		require.NoError(t, err)

		user := &User{
			Username:     "resetuser",
			PasswordHash: hashedPassword,
		}
		err = repo.CreateUser(ctx, user)
		require.NoError(t, err)

		// Test password reset request
		err = userService.RequestPasswordReset(ctx, "resetuser")
		require.NoError(t, err)

		// Verify user is flagged for reset
		userAfterRequest, err := repo.GetUserByUsername(ctx, "resetuser")
		require.NoError(t, err)
		assert.True(t, userAfterRequest.AllowPasswordReset)

		// Test password reset completion
		newPassword := "newpassword123"
		err = userService.CompletePasswordReset(ctx, "resetuser", newPassword)
		require.NoError(t, err)

		// Verify password was updated and reset flag was cleared
		userAfterReset, err := repo.GetUserByUsername(ctx, "resetuser")
		require.NoError(t, err)
		assert.False(t, userAfterReset.AllowPasswordReset)

		// Verify new password works by authenticating with the new password
		authenticatedUser, err := userService.Authenticate(ctx, "resetuser", newPassword)
		require.NoError(t, err)
		assert.NotNil(t, authenticatedUser)
		assert.Equal(t, "resetuser", authenticatedUser.Username)
	})
}

// Helper function to create string pointers
func ptrString(s string) *string {
	return &s
}
