package services

import (
	"fmt"
	"testing"
	"time"

	"github.com/Wikid82/charon/backend/internal/config"
	"github.com/Wikid82/charon/backend/internal/models"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

func setupAuthTestDB(t *testing.T) *gorm.DB {
	dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
	db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.AutoMigrate(&models.User{}))
	return db
}

func TestAuthService_Register(t *testing.T) {
	db := setupAuthTestDB(t)
	cfg := config.Config{JWTSecret: "test-secret"}
	service := NewAuthService(db, cfg)

	// Test 1: First user should be admin
	admin, err := service.Register("admin@example.com", "password123", "Admin User")
	require.NoError(t, err)
	assert.Equal(t, "admin", admin.Role)
	assert.NotEmpty(t, admin.PasswordHash)
	assert.NotEqual(t, "password123", admin.PasswordHash)

	// Test 2: Second user should be regular user
	user, err := service.Register("user@example.com", "password123", "Regular User")
	require.NoError(t, err)
	assert.Equal(t, "user", user.Role)
}

func TestAuthService_Login(t *testing.T) {
	db := setupAuthTestDB(t)
	cfg := config.Config{JWTSecret: "test-secret"}
	service := NewAuthService(db, cfg)

	// Setup user
	_, err := service.Register("test@example.com", "password123", "Test User")
	require.NoError(t, err)

	// Test 1: Successful login
	token, err := service.Login("test@example.com", "password123")
	require.NoError(t, err)
	assert.NotEmpty(t, token)

	// Test 2: Invalid password
	token, err = service.Login("test@example.com", "wrongpassword")
	assert.Error(t, err)
	assert.Empty(t, token)
	assert.Equal(t, "invalid credentials", err.Error())

	// Test 3: Account locking
	// Fail 4 more times (total 5)
	for i := 0; i < 4; i++ {
		_, err = service.Login("test@example.com", "wrongpassword")
		assert.Error(t, err)
	}

	// Check if locked
	var user models.User
	db.Where("email = ?", "test@example.com").First(&user)
	assert.Equal(t, 5, user.FailedLoginAttempts)
	assert.NotNil(t, user.LockedUntil)
	assert.True(t, user.LockedUntil.After(time.Now()))

	// Try login with correct password while locked
	token, err = service.Login("test@example.com", "password123")
	assert.Error(t, err)
	assert.Equal(t, "account locked", err.Error())
}

func TestAuthService_ChangePassword(t *testing.T) {
	db := setupAuthTestDB(t)
	cfg := config.Config{JWTSecret: "test-secret"}
	service := NewAuthService(db, cfg)

	user, err := service.Register("test@example.com", "password123", "Test User")
	require.NoError(t, err)

	// Success
	err = service.ChangePassword(user.ID, "password123", "newpassword")
	assert.NoError(t, err)

	// Verify login with new password
	_, err = service.Login("test@example.com", "newpassword")
	assert.NoError(t, err)

	// Fail with old password
	_, err = service.Login("test@example.com", "password123")
	assert.Error(t, err)

	// Fail with wrong current password
	err = service.ChangePassword(user.ID, "wrong", "another")
	assert.Error(t, err)
	assert.Equal(t, "invalid current password", err.Error())

	// Fail with non-existent user
	err = service.ChangePassword(999, "password", "new")
	assert.Error(t, err)
}

func TestAuthService_ValidateToken(t *testing.T) {
	db := setupAuthTestDB(t)
	cfg := config.Config{JWTSecret: "test-secret"}
	service := NewAuthService(db, cfg)

	user, err := service.Register("test@example.com", "password123", "Test User")
	require.NoError(t, err)

	token, err := service.Login("test@example.com", "password123")
	require.NoError(t, err)

	// Valid token
	claims, err := service.ValidateToken(token)
	assert.NoError(t, err)
	assert.Equal(t, user.ID, claims.UserID)

	// Invalid token
	_, err = service.ValidateToken("invalid.token.string")
	assert.Error(t, err)
}

func TestAuthService_GetUserByID(t *testing.T) {
	db := setupAuthTestDB(t)
	cfg := config.Config{JWTSecret: "test-secret"}
	service := NewAuthService(db, cfg)

	// Setup user
	user, err := service.Register("test@example.com", "password123", "Test User")
	require.NoError(t, err)

	// Test 1: Get existing user
	foundUser, err := service.GetUserByID(user.ID)
	require.NoError(t, err)
	assert.Equal(t, user.ID, foundUser.ID)
	assert.Equal(t, user.Email, foundUser.Email)

	// Test 2: Get non-existent user
	_, err = service.GetUserByID(999)
	assert.Error(t, err)
}
