//go:build integration
// +build integration

package handlers

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"veza-backend-api/internal/core/auth"
	"veza-backend-api/internal/database"
	"veza-backend-api/internal/dto"
	apperrors "veza-backend-api/internal/errors"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/repositories"
	"veza-backend-api/internal/services"
	"veza-backend-api/internal/validators"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
	"go.uber.org/zap/zaptest"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// setupAuthIntegrationTestRouter creates a test router with all auth services for integration testing
func setupAuthIntegrationTestRouter(t *testing.T) (*gin.Engine, *auth.AuthService, *services.SessionService, *services.TwoFactorService, *services.UserService, *gorm.DB, func()) {
	gin.SetMode(gin.TestMode)
	logger := zaptest.NewLogger(t)

	// Setup in-memory database
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Enable foreign keys for SQLite
	db.Exec("PRAGMA foreign_keys = ON")

	// Auto-migrate all models needed for auth flow
	err = db.AutoMigrate(
		&models.User{},
		&models.RefreshToken{},
		&models.Session{},
		&models.Role{},
		&models.Permission{},
		&models.UserRole{},
		&models.RolePermission{},
	)
	require.NoError(t, err)

	// Create email_verification_tokens table manually (no GORM model)
	err = db.Exec(`
		CREATE TABLE IF NOT EXISTS email_verification_tokens (
			id TEXT PRIMARY KEY,
			user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
			token TEXT NOT NULL UNIQUE,
			token_hash TEXT NOT NULL,
			email TEXT NOT NULL,
			verified INTEGER NOT NULL DEFAULT 0,
			used INTEGER NOT NULL DEFAULT 0,
			verified_at TIMESTAMP,
			expires_at TIMESTAMP NOT NULL,
			created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
		)
	`).Error
	require.NoError(t, err)

	// Create database wrapper
	dbWrapper := &database.Database{}
	dbWrapper.GormDB = db

	// Setup services
	emailValidator := validators.NewEmailValidator(db)
	passwordValidator := validators.NewPasswordValidator()
	passwordService := services.NewPasswordService(dbWrapper, logger)
	jwtService, err := services.NewJWTService("test-secret-key-must-be-32-chars-long", "test-issuer", "test-audience")
	require.NoError(t, err)
	refreshTokenService := services.NewRefreshTokenService(db)
	emailVerificationService := services.NewEmailVerificationService(dbWrapper, logger)
	passwordResetService := services.NewPasswordResetService(dbWrapper, logger)
	emailService := services.NewEmailService(dbWrapper, logger)

	// Create AuthService
	authService := auth.NewAuthService(
		db,
		emailValidator,
		passwordValidator,
		passwordService,
		jwtService,
		refreshTokenService,
		emailVerificationService,
		passwordResetService,
		emailService,
		nil, // jobWorker - not needed for integration tests
		logger,
	)

	// Create other services
	sessionService := services.NewSessionService(dbWrapper, logger)
	twoFactorService := services.NewTwoFactorService(dbWrapper, logger)

	// Create UserService with GORM repository
	userRepo := repositories.NewGormUserRepository(db)
	userService := services.NewUserServiceWithDB(userRepo, db)

	// Setup router with all auth endpoints
	router := gin.New()
	authGroup := router.Group("/auth")
	{
		authGroup.POST("/login", Login(authService, sessionService, twoFactorService, logger))
		authGroup.POST("/register", Register(authService, logger))
		authGroup.POST("/refresh", Refresh(authService, logger))
		authGroup.POST("/logout", Logout(authService, sessionService, logger))
		authGroup.POST("/verify-email", VerifyEmail(authService))
		authGroup.POST("/resend-verification", ResendVerification(authService, logger))
		authGroup.GET("/check-username", CheckUsername(authService))

		// Protected route for /me
		protected := authGroup.Group("")
		protected.Use(func(c *gin.Context) {
			// Mock auth middleware - extract token from Authorization header
			authHeader := c.GetHeader("Authorization")
			if authHeader == "" {
				RespondWithAppError(c, apperrors.NewUnauthorizedError("Unauthorized"))
				c.Abort()
				return
			}

			// Extract token (simplified - in real app, would validate JWT)
			if !strings.HasPrefix(authHeader, "Bearer ") {
				RespondWithAppError(c, apperrors.NewUnauthorizedError("Invalid authorization header"))
				c.Abort()
				return
			}

			token := strings.TrimPrefix(authHeader, "Bearer ")

			// Validate token and extract user ID
			claims, err := authService.JWTService.ValidateToken(token)
			if err != nil {
				RespondWithAppError(c, apperrors.NewUnauthorizedError("Invalid token"))
				c.Abort()
				return
			}

			// Extract user ID from claims
			uid := claims.UserID
			if uid == uuid.Nil {
				RespondWithAppError(c, apperrors.NewUnauthorizedError("Invalid token claims"))
				c.Abort()
				return
			}

			c.Set("user_id", uid)
			c.Next()
		})
		protected.GET("/me", GetMe(userService))
	}

	cleanup := func() {
		// Database cleanup handled by test
	}

	return router, authService, sessionService, twoFactorService, userService, db, cleanup
}

// TestAuthFlow_CompleteFlow tests the complete authentication flow: Register -> Login -> Refresh -> Logout
func TestAuthFlow_CompleteFlow(t *testing.T) {
	router, _, _, _, _, db, cleanup := setupAuthIntegrationTestRouter(t)
	defer cleanup()

	// Step 1: Register a new user
	registerReq := dto.RegisterRequest{
		Email:           "test@example.com",
		Username:        "testuser",
		Password:        "SecurePassword123!",
		PasswordConfirm: "SecurePassword123!",
	}
	registerBody, _ := json.Marshal(registerReq)
	registerHTTPReq := httptest.NewRequest(http.MethodPost, "/auth/register", bytes.NewBuffer(registerBody))
	registerHTTPReq.Header.Set("Content-Type", "application/json")
	registerW := httptest.NewRecorder()
	router.ServeHTTP(registerW, registerHTTPReq)

	assert.Equal(t, http.StatusCreated, registerW.Code)
	var registerResponse APIResponse
	err := json.Unmarshal(registerW.Body.Bytes(), &registerResponse)
	require.NoError(t, err)
	assert.True(t, registerResponse.Success)

	// Get user from database to verify email
	var user models.User
	err = db.Where("email = ?", "test@example.com").First(&user).Error
	require.NoError(t, err)

	// Step 2: Verify email (simulate email verification)
	// Get verification token from database (using raw SQL since there's no GORM model)
	var token string
	err = db.Raw("SELECT token FROM email_verification_tokens WHERE user_id = ? AND used = 0 ORDER BY created_at DESC LIMIT 1", user.ID.String()).Scan(&token).Error
	if err != nil {
		// If no token found, try to get from emailVerificationService
		// For now, we'll skip this test if token not found
		t.Skip("Email verification token not found in database")
		return
	}

	verifyReq := httptest.NewRequest(http.MethodPost, "/auth/verify-email?token="+token, nil)
	verifyW := httptest.NewRecorder()
	router.ServeHTTP(verifyW, verifyReq)

	assert.Equal(t, http.StatusOK, verifyW.Code)

	// Step 3: Login
	loginReq := dto.LoginRequest{
		Email:      "test@example.com",
		Password:   "SecurePassword123!",
		RememberMe: false,
	}
	loginBody, _ := json.Marshal(loginReq)
	loginHTTPReq := httptest.NewRequest(http.MethodPost, "/auth/login", bytes.NewBuffer(loginBody))
	loginHTTPReq.Header.Set("Content-Type", "application/json")
	loginW := httptest.NewRecorder()
	router.ServeHTTP(loginW, loginHTTPReq)

	assert.Equal(t, http.StatusOK, loginW.Code)
	var loginResponse APIResponse
	err = json.Unmarshal(loginW.Body.Bytes(), &loginResponse)
	require.NoError(t, err)
	assert.True(t, loginResponse.Success)

	// Extract tokens from response
	loginDataBytes, _ := json.Marshal(loginResponse.Data)
	var loginData dto.LoginResponse
	err = json.Unmarshal(loginDataBytes, &loginData)
	require.NoError(t, err)
	assert.NotEmpty(t, loginData.Token.AccessToken)
	assert.NotEmpty(t, loginData.Token.RefreshToken)

	// Step 4: Use access token to access protected endpoint
	meReq := httptest.NewRequest(http.MethodGet, "/auth/me", nil)
	meReq.Header.Set("Authorization", "Bearer "+loginData.Token.AccessToken)
	meW := httptest.NewRecorder()
	router.ServeHTTP(meW, meReq)

	assert.Equal(t, http.StatusOK, meW.Code)
	var meResponse APIResponse
	err = json.Unmarshal(meW.Body.Bytes(), &meResponse)
	require.NoError(t, err)
	assert.True(t, meResponse.Success)

	// Step 5: Refresh token
	refreshReq := dto.RefreshRequest{
		RefreshToken: loginData.Token.RefreshToken,
	}
	refreshBody, _ := json.Marshal(refreshReq)
	refreshHTTPReq := httptest.NewRequest(http.MethodPost, "/auth/refresh", bytes.NewBuffer(refreshBody))
	refreshHTTPReq.Header.Set("Content-Type", "application/json")
	refreshW := httptest.NewRecorder()
	router.ServeHTTP(refreshW, refreshHTTPReq)

	assert.Equal(t, http.StatusOK, refreshW.Code)
	var refreshResponse APIResponse
	err = json.Unmarshal(refreshW.Body.Bytes(), &refreshResponse)
	require.NoError(t, err)
	assert.True(t, refreshResponse.Success)

	// Extract new tokens
	refreshDataBytes, _ := json.Marshal(refreshResponse.Data)
	var refreshData dto.TokenResponse
	err = json.Unmarshal(refreshDataBytes, &refreshData)
	require.NoError(t, err)
	assert.NotEmpty(t, refreshData.AccessToken)
	assert.NotEmpty(t, refreshData.RefreshToken)

	// Step 6: Logout
	logoutReqBody := map[string]string{
		"refresh_token": refreshData.RefreshToken,
	}
	logoutBody, _ := json.Marshal(logoutReqBody)
	logoutHTTPReq := httptest.NewRequest(http.MethodPost, "/auth/logout", bytes.NewBuffer(logoutBody))
	logoutHTTPReq.Header.Set("Content-Type", "application/json")
	logoutHTTPReq.Header.Set("Authorization", "Bearer "+refreshData.AccessToken)
	logoutW := httptest.NewRecorder()
	router.ServeHTTP(logoutW, logoutHTTPReq)

	assert.Equal(t, http.StatusOK, logoutW.Code)
	var logoutResponse APIResponse
	err = json.Unmarshal(logoutW.Body.Bytes(), &logoutResponse)
	require.NoError(t, err)
	assert.True(t, logoutResponse.Success)
}

// TestAuthFlow_EmailVerificationFlow tests the email verification flow: Register -> Login fails -> Verify -> Login succeeds
func TestAuthFlow_EmailVerificationFlow(t *testing.T) {
	router, _, _, _, _, db, cleanup := setupAuthIntegrationTestRouter(t)
	defer cleanup()

	// Step 1: Register a new user
	registerReq := dto.RegisterRequest{
		Email:           "test2@example.com",
		Username:        "testuser2",
		Password:        "SecurePassword123!",
		PasswordConfirm: "SecurePassword123!",
	}
	registerBody, _ := json.Marshal(registerReq)
	registerHTTPReq := httptest.NewRequest(http.MethodPost, "/auth/register", bytes.NewBuffer(registerBody))
	registerHTTPReq.Header.Set("Content-Type", "application/json")
	registerW := httptest.NewRecorder()
	router.ServeHTTP(registerW, registerHTTPReq)

	assert.Equal(t, http.StatusCreated, registerW.Code)

	// Step 2: Try to login before email verification (should fail)
	loginReq := dto.LoginRequest{
		Email:      "test2@example.com",
		Password:   "SecurePassword123!",
		RememberMe: false,
	}
	loginBody, _ := json.Marshal(loginReq)
	loginHTTPReq := httptest.NewRequest(http.MethodPost, "/auth/login", bytes.NewBuffer(loginBody))
	loginHTTPReq.Header.Set("Content-Type", "application/json")
	loginW := httptest.NewRecorder()
	router.ServeHTTP(loginW, loginHTTPReq)

	assert.Equal(t, http.StatusForbidden, loginW.Code) // Email not verified

	// Step 3: Get verification token and verify email
	var user models.User
	err := db.Where("email = ?", "test2@example.com").First(&user).Error
	require.NoError(t, err)

	// Get verification token from database
	var token string
	err = db.Raw("SELECT token FROM email_verification_tokens WHERE user_id = ? AND used = 0 ORDER BY created_at DESC LIMIT 1", user.ID.String()).Scan(&token).Error
	if err != nil {
		t.Skip("Email verification token not found in database")
		return
	}

	verifyReq := httptest.NewRequest(http.MethodPost, "/auth/verify-email?token="+token, nil)
	verifyW := httptest.NewRecorder()
	router.ServeHTTP(verifyW, verifyReq)

	assert.Equal(t, http.StatusOK, verifyW.Code)

	// Step 4: Try to login after email verification (should succeed)
	loginHTTPReq2 := httptest.NewRequest(http.MethodPost, "/auth/login", bytes.NewBuffer(loginBody))
	loginHTTPReq2.Header.Set("Content-Type", "application/json")
	loginW2 := httptest.NewRecorder()
	router.ServeHTTP(loginW2, loginHTTPReq2)

	assert.Equal(t, http.StatusOK, loginW2.Code)
	var loginResponse APIResponse
	err = json.Unmarshal(loginW2.Body.Bytes(), &loginResponse)
	require.NoError(t, err)
	assert.True(t, loginResponse.Success)
}

// TestAuthFlow_CheckUsername tests username availability checking
func TestAuthFlow_CheckUsername(t *testing.T) {
	router, _, _, _, _, _, cleanup := setupAuthIntegrationTestRouter(t)
	defer cleanup()

	// Step 1: Register a user
	registerReq := dto.RegisterRequest{
		Email:           "test3@example.com",
		Username:        "testuser3",
		Password:        "SecurePassword123!",
		PasswordConfirm: "SecurePassword123!",
	}
	registerBody, _ := json.Marshal(registerReq)
	registerHTTPReq := httptest.NewRequest(http.MethodPost, "/auth/register", bytes.NewBuffer(registerBody))
	registerHTTPReq.Header.Set("Content-Type", "application/json")
	registerW := httptest.NewRecorder()
	router.ServeHTTP(registerW, registerHTTPReq)

	assert.Equal(t, http.StatusCreated, registerW.Code)

	// Step 2: Check if username is available (should be false - already taken)
	checkReq := httptest.NewRequest(http.MethodGet, "/auth/check-username?username=testuser3", nil)
	checkW := httptest.NewRecorder()
	router.ServeHTTP(checkW, checkReq)

	assert.Equal(t, http.StatusOK, checkW.Code)
	var checkResponse APIResponse
	err := json.Unmarshal(checkW.Body.Bytes(), &checkResponse)
	require.NoError(t, err)
	assert.True(t, checkResponse.Success)

	checkDataBytes, _ := json.Marshal(checkResponse.Data)
	var checkData map[string]interface{}
	err = json.Unmarshal(checkDataBytes, &checkData)
	require.NoError(t, err)
	assert.Equal(t, false, checkData["available"])

	// Step 3: Check if different username is available (should be true)
	checkReq2 := httptest.NewRequest(http.MethodGet, "/auth/check-username?username=newuser", nil)
	checkW2 := httptest.NewRecorder()
	router.ServeHTTP(checkW2, checkReq2)

	assert.Equal(t, http.StatusOK, checkW2.Code)
	var checkResponse2 APIResponse
	err = json.Unmarshal(checkW2.Body.Bytes(), &checkResponse2)
	require.NoError(t, err)
	assert.True(t, checkResponse2.Success)

	checkDataBytes2, _ := json.Marshal(checkResponse2.Data)
	var checkData2 map[string]interface{}
	err = json.Unmarshal(checkDataBytes2, &checkData2)
	require.NoError(t, err)
	assert.Equal(t, true, checkData2["available"])
}

// TestAuthFlow_ResendVerification tests resending verification email
func TestAuthFlow_ResendVerification(t *testing.T) {
	router, _, _, _, _, _, cleanup := setupAuthIntegrationTestRouter(t)
	defer cleanup()

	// Step 1: Register a user
	registerReq := dto.RegisterRequest{
		Email:           "test4@example.com",
		Username:        "testuser4",
		Password:        "SecurePassword123!",
		PasswordConfirm: "SecurePassword123!",
	}
	registerBody, _ := json.Marshal(registerReq)
	registerHTTPReq := httptest.NewRequest(http.MethodPost, "/auth/register", bytes.NewBuffer(registerBody))
	registerHTTPReq.Header.Set("Content-Type", "application/json")
	registerW := httptest.NewRecorder()
	router.ServeHTTP(registerW, registerHTTPReq)

	assert.Equal(t, http.StatusCreated, registerW.Code)

	// Step 2: Resend verification email
	resendReq := dto.ResendVerificationRequest{
		Email: "test4@example.com",
	}
	resendBody, _ := json.Marshal(resendReq)
	resendHTTPReq := httptest.NewRequest(http.MethodPost, "/auth/resend-verification", bytes.NewBuffer(resendBody))
	resendHTTPReq.Header.Set("Content-Type", "application/json")
	resendW := httptest.NewRecorder()
	router.ServeHTTP(resendW, resendHTTPReq)

	assert.Equal(t, http.StatusOK, resendW.Code)
	var resendResponse APIResponse
	err := json.Unmarshal(resendW.Body.Bytes(), &resendResponse)
	require.NoError(t, err)
	assert.True(t, resendResponse.Success)
}

// TestAuthFlow_InvalidRefreshToken tests refresh with invalid token
func TestAuthFlow_InvalidRefreshToken(t *testing.T) {
	router, _, _, _, _, _, cleanup := setupAuthIntegrationTestRouter(t)
	defer cleanup()

	// Try to refresh with invalid token
	refreshReq := dto.RefreshRequest{
		RefreshToken: "invalid-refresh-token",
	}
	refreshBody, _ := json.Marshal(refreshReq)
	refreshHTTPReq := httptest.NewRequest(http.MethodPost, "/auth/refresh", bytes.NewBuffer(refreshBody))
	refreshHTTPReq.Header.Set("Content-Type", "application/json")
	refreshW := httptest.NewRecorder()
	router.ServeHTTP(refreshW, refreshHTTPReq)

	assert.Equal(t, http.StatusUnauthorized, refreshW.Code)
}

// TestAuthFlow_DuplicateRegistration tests registering with existing email/username
func TestAuthFlow_DuplicateRegistration(t *testing.T) {
	router, _, _, _, _, _, cleanup := setupAuthIntegrationTestRouter(t)
	defer cleanup()

	// Step 1: Register a user
	registerReq := dto.RegisterRequest{
		Email:           "test5@example.com",
		Username:        "testuser5",
		Password:        "SecurePassword123!",
		PasswordConfirm: "SecurePassword123!",
	}
	registerBody, _ := json.Marshal(registerReq)
	registerHTTPReq := httptest.NewRequest(http.MethodPost, "/auth/register", bytes.NewBuffer(registerBody))
	registerHTTPReq.Header.Set("Content-Type", "application/json")
	registerW := httptest.NewRecorder()
	router.ServeHTTP(registerW, registerHTTPReq)

	assert.Equal(t, http.StatusCreated, registerW.Code)

	// Step 2: Try to register again with same email (should fail)
	registerHTTPReq2 := httptest.NewRequest(http.MethodPost, "/auth/register", bytes.NewBuffer(registerBody))
	registerHTTPReq2.Header.Set("Content-Type", "application/json")
	registerW2 := httptest.NewRecorder()
	router.ServeHTTP(registerW2, registerHTTPReq2)

	assert.Equal(t, http.StatusConflict, registerW2.Code)
}
