package handlers

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

	"veza-backend-api/internal/database"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/repositories"
	"veza-backend-api/internal/services"

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

// setupTestProfileHandler creates a test handler with real services and in-memory database
func setupTestProfileHandler(t *testing.T) (*ProfileHandler, *gorm.DB, *gin.Engine, func()) {
	gin.SetMode(gin.TestMode)
	logger := zaptest.NewLogger(t)

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

	// Auto-migrate models
	err = db.AutoMigrate(
		&models.User{},
		&models.Role{},
		&models.Permission{},
		&models.UserRole{},
		&models.RolePermission{},
	)
	require.NoError(t, err)

	// Create follows and user_blocks tables for SocialService (uses raw SQL)
	sqlDB, err := db.DB()
	require.NoError(t, err)
	_, err = sqlDB.Exec(`
		CREATE TABLE IF NOT EXISTS follows (
			id TEXT PRIMARY KEY,
			follower_id TEXT NOT NULL,
			followed_id TEXT NOT NULL,
			created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
			UNIQUE(follower_id, followed_id)
		)
	`)
	require.NoError(t, err)
	_, err = sqlDB.Exec(`
		CREATE TABLE IF NOT EXISTS user_blocks (
			id TEXT PRIMARY KEY,
			blocker_id TEXT NOT NULL,
			blocked_id TEXT NOT NULL,
			created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
			UNIQUE(blocker_id, blocked_id)
		)
	`)
	require.NoError(t, err)

	// Setup repositories
	userRepo := repositories.NewGormUserRepository(db)

	// Setup services
	userService := services.NewUserServiceWithDB(userRepo, db)
	permissionService := services.NewPermissionService(db)

	// Create database wrapper for SocialService
	// Note: SocialService uses raw SQL, so we need to get the underlying sql.DB
	sqlDB, err = db.DB()
	require.NoError(t, err)
	dbWrapper := &database.Database{}
	dbWrapper.DB = sqlDB
	dbWrapper.GormDB = db
	socialService := services.NewSocialService(dbWrapper, logger)

	handler := NewProfileHandler(userService, logger)
	handler.SetSocialService(socialService)
	handler.SetPermissionService(permissionService)

	router := gin.New()
	router.Use(func(c *gin.Context) {
		// Mock auth middleware - set user_id from header if present
		userIDStr := c.GetHeader("X-User-ID")
		if userIDStr != "" {
			uid, err := uuid.Parse(userIDStr)
			if err == nil {
				c.Set("user_id", uid)
			}
		}
		c.Next()
	})

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

	return handler, db, router, cleanup
}

// Helper to create a test user
func createTestUserForProfile(id uuid.UUID, username string) *models.User {
	return &models.User{
		ID:         id,
		Username:   username,
		Email:      fmt.Sprintf("%s@example.com", username),
		IsActive:   true,
		IsVerified: true,
		CreatedAt:  time.Now(),
		UpdatedAt:  time.Now(),
	}
}

// TestProfileHandler_GetProfile_Success tests successful profile retrieval
func TestProfileHandler_GetProfile_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForProfile(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.GET("/users/:id", handler.GetProfile)

	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/users/%s", userID.String()), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
}

// TestProfileHandler_GetProfile_NotFound tests profile not found scenario
func TestProfileHandler_GetProfile_NotFound(t *testing.T) {
	handler, _, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	router.GET("/users/:id", handler.GetProfile)

	nonExistentID := uuid.New()
	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/users/%s", nonExistentID.String()), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
}

// TestProfileHandler_GetProfile_InvalidID tests invalid user ID format
func TestProfileHandler_GetProfile_InvalidID(t *testing.T) {
	handler, _, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	router.GET("/users/:id", handler.GetProfile)

	req := httptest.NewRequest(http.MethodGet, "/users/invalid-id", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// TestProfileHandler_GetProfileByUsername_Success tests successful profile retrieval by username
func TestProfileHandler_GetProfileByUsername_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForProfile(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.GET("/users/by-username/:username", handler.GetProfileByUsername)

	req := httptest.NewRequest(http.MethodGet, "/users/by-username/testuser", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
}

// TestProfileHandler_GetProfileByUsername_NotFound tests username not found scenario
func TestProfileHandler_GetProfileByUsername_NotFound(t *testing.T) {
	handler, _, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	router.GET("/users/by-username/:username", handler.GetProfileByUsername)

	req := httptest.NewRequest(http.MethodGet, "/users/by-username/nonexistent", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
}

// TestProfileHandler_ListUsers_Success tests successful user listing
func TestProfileHandler_ListUsers_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test users
	for i := 0; i < 3; i++ {
		user := createTestUserForProfile(uuid.New(), fmt.Sprintf("user%d", i+1))
		err := db.Create(user).Error
		require.NoError(t, err)
	}

	router.GET("/users", handler.ListUsers)

	req := httptest.NewRequest(http.MethodGet, "/users?page=1&limit=10", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
}

// TestProfileHandler_SearchUsers_Success tests successful user search
func TestProfileHandler_SearchUsers_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForProfile(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.GET("/users/search", handler.SearchUsers)

	req := httptest.NewRequest(http.MethodGet, "/users/search?q=test&page=1&limit=10", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
}

// TestProfileHandler_UpdateProfile_Success tests successful profile update
func TestProfileHandler_UpdateProfile_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForProfile(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.PUT("/users/:id", handler.UpdateProfile)

	updateReq := UpdateProfileRequest{
		FirstName: "John",
		LastName:  "Doe",
		Bio:       "Test bio",
	}
	body, _ := json.Marshal(updateReq)
	req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/users/%s", userID.String()), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
}

// TestProfileHandler_UpdateProfile_Forbidden tests forbidden profile update (different user)
func TestProfileHandler_UpdateProfile_Forbidden(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test users
	userID1 := uuid.New()
	user1 := createTestUserForProfile(userID1, "user1")
	err := db.Create(user1).Error
	require.NoError(t, err)

	userID2 := uuid.New()
	user2 := createTestUserForProfile(userID2, "user2")
	err = db.Create(user2).Error
	require.NoError(t, err)

	router.PUT("/users/:id", handler.UpdateProfile)

	updateReq := UpdateProfileRequest{
		FirstName: "John",
	}
	body, _ := json.Marshal(updateReq)
	// Try to update user1's profile as user2
	req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/users/%s", userID1.String()), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID2.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

// TestProfileHandler_GetProfileCompletion_Success tests successful profile completion retrieval
func TestProfileHandler_GetProfileCompletion_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForProfile(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.GET("/users/:id/completion", handler.GetProfileCompletion)

	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/users/%s/completion", userID.String()), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
}

// TestProfileHandler_GetProfileCompletion_Forbidden tests forbidden access to other user's completion
func TestProfileHandler_GetProfileCompletion_Forbidden(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test users
	userID1 := uuid.New()
	user1 := createTestUserForProfile(userID1, "user1")
	err := db.Create(user1).Error
	require.NoError(t, err)

	userID2 := uuid.New()
	user2 := createTestUserForProfile(userID2, "user2")
	err = db.Create(user2).Error
	require.NoError(t, err)

	router.GET("/users/:id/completion", handler.GetProfileCompletion)

	// Try to access user1's completion as user2
	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/users/%s/completion", userID1.String()), nil)
	req.Header.Set("X-User-ID", userID2.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

// TestProfileHandler_FollowUser_Success tests successful user follow
func TestProfileHandler_FollowUser_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test users
	followerID := uuid.New()
	follower := createTestUserForProfile(followerID, "follower")
	err := db.Create(follower).Error
	require.NoError(t, err)

	followedID := uuid.New()
	followed := createTestUserForProfile(followedID, "followed")
	err = db.Create(followed).Error
	require.NoError(t, err)

	router.POST("/users/:id/follow", handler.FollowUser)

	req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/users/%s/follow", followedID.String()), nil)
	req.Header.Set("X-User-ID", followerID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
}

// TestProfileHandler_FollowUser_CannotFollowSelf tests that user cannot follow themselves
func TestProfileHandler_FollowUser_CannotFollowSelf(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForProfile(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.POST("/users/:id/follow", handler.FollowUser)

	req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/users/%s/follow", userID.String()), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// TestProfileHandler_UnfollowUser_Success tests successful user unfollow
func TestProfileHandler_UnfollowUser_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test users
	followerID := uuid.New()
	follower := createTestUserForProfile(followerID, "follower")
	err := db.Create(follower).Error
	require.NoError(t, err)

	followedID := uuid.New()
	followed := createTestUserForProfile(followedID, "followed")
	err = db.Create(followed).Error
	require.NoError(t, err)

	// Create follow relationship using raw SQL (follows table)
	sqlDB, err := db.DB()
	require.NoError(t, err)
	_, err = sqlDB.Exec("INSERT INTO follows (follower_id, followed_id) VALUES (?, ?)", followerID, followedID)
	require.NoError(t, err)

	router.DELETE("/users/:id/follow", handler.UnfollowUser)

	req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/users/%s/follow", followedID.String()), nil)
	req.Header.Set("X-User-ID", followerID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
}

// TestProfileHandler_BlockUser_Success tests successful user block
func TestProfileHandler_BlockUser_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test users
	blockerID := uuid.New()
	blocker := createTestUserForProfile(blockerID, "blocker")
	err := db.Create(blocker).Error
	require.NoError(t, err)

	blockedID := uuid.New()
	blocked := createTestUserForProfile(blockedID, "blocked")
	err = db.Create(blocked).Error
	require.NoError(t, err)

	router.POST("/users/:id/block", handler.BlockUser)

	req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/users/%s/block", blockedID.String()), nil)
	req.Header.Set("X-User-ID", blockerID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
}

// TestProfileHandler_BlockUser_CannotBlockSelf tests that user cannot block themselves
func TestProfileHandler_BlockUser_CannotBlockSelf(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForProfile(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.POST("/users/:id/block", handler.BlockUser)

	req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/users/%s/block", userID.String()), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// TestProfileHandler_UnblockUser_Success tests successful user unblock
func TestProfileHandler_UnblockUser_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test users
	blockerID := uuid.New()
	blocker := createTestUserForProfile(blockerID, "blocker")
	err := db.Create(blocker).Error
	require.NoError(t, err)

	blockedID := uuid.New()
	blocked := createTestUserForProfile(blockedID, "blocked")
	err = db.Create(blocked).Error
	require.NoError(t, err)

	// Create block relationship using raw SQL (user_blocks table)
	sqlDB, err := db.DB()
	require.NoError(t, err)
	_, err = sqlDB.Exec("INSERT INTO user_blocks (blocker_id, blocked_id) VALUES (?, ?)", blockerID, blockedID)
	require.NoError(t, err)

	router.DELETE("/users/:id/block", handler.UnblockUser)

	req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/users/%s/block", blockedID.String()), nil)
	req.Header.Set("X-User-ID", blockerID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
}

// TestProfileHandler_DeleteUser_Success tests successful user deletion
func TestProfileHandler_DeleteUser_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	user := createTestUserForProfile(userID, "testuser")
	err := db.Create(user).Error
	require.NoError(t, err)

	router.DELETE("/users/:id", handler.DeleteUser)

	req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/users/%s", userID.String()), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
}

// TestProfileHandler_DeleteUser_Forbidden tests forbidden user deletion (different user)
func TestProfileHandler_DeleteUser_Forbidden(t *testing.T) {
	handler, db, router, cleanup := setupTestProfileHandler(t)
	defer cleanup()

	// Create test users
	userID1 := uuid.New()
	user1 := createTestUserForProfile(userID1, "user1")
	err := db.Create(user1).Error
	require.NoError(t, err)

	userID2 := uuid.New()
	user2 := createTestUserForProfile(userID2, "user2")
	err = db.Create(user2).Error
	require.NoError(t, err)

	router.DELETE("/users/:id", handler.DeleteUser)

	// Try to delete user1 as user2
	req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/users/%s", userID1.String()), nil)
	req.Header.Set("X-User-ID", userID2.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}
