package user

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

	"veza-backend-api/internal/common"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

// MockUserService is a mock implementation of Service for testing
type MockUserService struct {
	mock.Mock
}

func (m *MockUserService) GetUserByID(userID uuid.UUID) (*UserResponse, error) {
	args := m.Called(userID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*UserResponse), args.Error(1)
}

func (m *MockUserService) GetUsers(page, limit int, search string) ([]UserResponse, int, error) {
	args := m.Called(page, limit, search)
	if args.Get(0) == nil {
		return nil, args.Int(1), args.Error(2)
	}
	return args.Get(0).([]UserResponse), args.Int(1), args.Error(2)
}

func (m *MockUserService) UpdateUser(userID uuid.UUID, req UpdateUserRequest) (*UserResponse, error) {
	args := m.Called(userID, req)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*UserResponse), args.Error(1)
}

func (m *MockUserService) ChangePassword(userID uuid.UUID, currentPassword, newPassword string) error {
	args := m.Called(userID, currentPassword, newPassword)
	return args.Error(0)
}

func (m *MockUserService) GetUserPreferences(userID uuid.UUID) (*UserPreferencesResponse, error) {
	args := m.Called(userID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*UserPreferencesResponse), args.Error(1)
}

func (m *MockUserService) UpdateUserPreferences(userID uuid.UUID, req UserPreferencesRequest) (*UserPreferencesResponse, error) {
	args := m.Called(userID, req)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*UserPreferencesResponse), args.Error(1)
}

func (m *MockUserService) DeleteAccount(userID uuid.UUID, password, reason string) error {
	args := m.Called(userID, password, reason)
	return args.Error(0)
}

func (m *MockUserService) RecoverAccount(email, password string) error {
	args := m.Called(email, password)
	return args.Error(0)
}

func (m *MockUserService) RequestDataDeletion(userID uuid.UUID, password, reason string) error {
	args := m.Called(userID, password, reason)
	return args.Error(0)
}

func (m *MockUserService) GetAccountStatus(userID uuid.UUID) (*AccountStatus, error) {
	args := m.Called(userID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*AccountStatus), args.Error(1)
}

// MockDataExportService is a mock implementation of DataExportService for testing
type MockDataExportService struct {
	mock.Mock
}

func (m *MockDataExportService) ExportUserDataAsJSON(ctx context.Context, userID uuid.UUID) ([]byte, error) {
	args := m.Called(ctx, userID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]byte), args.Error(1)
}

// setupTestUserRouter creates a test router with user handlers
func setupTestUserRouter(mockService *MockUserService, mockDataExportService *MockDataExportService) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	handler := NewHandlerWithInterfaces(mockService, mockDataExportService)

	api := router.Group("/api/v1")
	api.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 {
				common.SetUserIDInContext(c, uid)
			}
		}
		c.Next()
	})

	users := api.Group("/users")
	{
		users.GET("/me", handler.GetMe)
		users.PATCH("/me", handler.UpdateMe)
		users.POST("/me/password", handler.ChangePassword)
		users.GET("", handler.GetUsers)
		users.GET("/except-me", handler.GetUsersExceptMe)
		users.GET("/search", handler.SearchUsers)
		users.GET("/:id/avatar", handler.GetUserAvatar)
		users.GET("/me/preferences", handler.GetPreferences)
		users.PATCH("/me/preferences", handler.UpdatePreferences)
		users.DELETE("/me", handler.DeleteAccount)
		users.POST("/recover", handler.RecoverAccount)
		users.GET("/me/export", handler.ExportData)
		users.POST("/me/delete-request", handler.RequestDataDeletion)
		users.GET("/me/status", handler.GetAccountStatus)
	}

	return router
}

func TestUserHandler_GetMe_Success(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	expectedUser := &UserResponse{
		ID:         userID,
		Email:      "test@example.com",
		Username:   "testuser",
		IsActive:   true,
		IsVerified: true,
		CreatedAt:  time.Now(),
		UpdatedAt:  time.Now(),
	}

	mockService.On("GetUserByID", userID).Return(expectedUser, nil)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/v1/users/me", nil)
	req.Header.Set("X-User-ID", userID.String())
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)

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

func TestUserHandler_GetMe_Unauthorized(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/v1/users/me", nil)
	// No X-User-ID header
	router.ServeHTTP(w, req)

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

func TestUserHandler_GetMe_NotFound(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	mockService.On("GetUserByID", userID).Return(nil, assert.AnError)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/v1/users/me", nil)
	req.Header.Set("X-User-ID", userID.String())
	router.ServeHTTP(w, req)

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

func TestUserHandler_UpdateMe_Success(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	username := "newusername"
	reqBody := UpdateUserRequest{
		Username: &username,
	}

	expectedUser := &UserResponse{
		ID:        userID,
		Username:  username,
		Email:     "test@example.com",
		IsActive:  true,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	mockService.On("UpdateUser", userID, reqBody).Return(expectedUser, nil)

	body, _ := json.Marshal(reqBody)
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("PATCH", "/api/v1/users/me", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)

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

func TestUserHandler_ChangePassword_Success(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	reqBody := map[string]interface{}{
		"current_password": "oldpass123",
		"new_password":     "newpass123",
	}

	mockService.On("ChangePassword", userID, "oldpass123", "newpass123").Return(nil)

	body, _ := json.Marshal(reqBody)
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("POST", "/api/v1/users/me/password", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)
}

func TestUserHandler_ChangePassword_InvalidRequest(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	reqBody := map[string]interface{}{
		"current_password": "oldpass123",
		// Missing new_password
	}

	body, _ := json.Marshal(reqBody)
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("POST", "/api/v1/users/me/password", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	router.ServeHTTP(w, req)

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

func TestUserHandler_GetUsers_Success(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	expectedUsers := []UserResponse{
		{
			ID:        uuid.New(),
			Username:  "user1",
			Email:     "user1@example.com",
			IsActive:  true,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		},
		{
			ID:        uuid.New(),
			Username:  "user2",
			Email:     "user2@example.com",
			IsActive:  true,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		},
	}

	mockService.On("GetUsers", 1, 20, "").Return(expectedUsers, 2, nil)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/v1/users?page=1&limit=20", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)

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

func TestUserHandler_SearchUsers_Success(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	query := "test"
	expectedUsers := []UserResponse{
		{
			ID:        uuid.New(),
			Username:  "testuser",
			Email:     "test@example.com",
			IsActive:  true,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		},
	}

	mockService.On("GetUsers", 1, 20, query).Return(expectedUsers, 1, nil)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/v1/users/search?q=test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)
}

func TestUserHandler_SearchUsers_MissingQuery(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/v1/users/search", nil)
	router.ServeHTTP(w, req)

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

func TestUserHandler_GetUserAvatar_Success(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	avatarURL := "https://example.com/avatar.jpg"
	expectedUser := &UserResponse{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		Avatar: sql.NullString{
			String: avatarURL,
			Valid:  true,
		},
		IsActive:  true,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	mockService.On("GetUserByID", userID).Return(expectedUser, nil)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/v1/users/"+userID.String()+"/avatar", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusFound, w.Code)
	assert.Equal(t, avatarURL, w.Header().Get("Location"))
	mockService.AssertExpectations(t)
}

func TestUserHandler_GetUserAvatar_NoAvatar(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	expectedUser := &UserResponse{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		Avatar: sql.NullString{
			Valid: false,
		},
		IsActive:  true,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	mockService.On("GetUserByID", userID).Return(expectedUser, nil)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/v1/users/"+userID.String()+"/avatar", nil)
	router.ServeHTTP(w, req)

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

func TestUserHandler_GetPreferences_Success(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	expectedPreferences := &UserPreferencesResponse{
		UserID:   userID,
		Theme:    "dark",
		Language: "en",
		Timezone: "UTC",
		Notifications: NotificationSettings{
			Email: true,
			Push:  false,
		},
		Privacy: PrivacySettings{
			ShowEmail: false,
		},
		Audio: AudioSettings{
			AutoPlay: true,
			Quality:  "high",
			Volume:   0.8,
		},
		UpdatedAt: time.Now(),
	}

	mockService.On("GetUserPreferences", userID).Return(expectedPreferences, nil)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/v1/users/me/preferences", nil)
	req.Header.Set("X-User-ID", userID.String())
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)

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

func TestUserHandler_DeleteAccount_Success(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	reqBody := map[string]interface{}{
		"password":     "password123",
		"reason":       "No longer needed",
		"confirm_text": "DELETE",
	}

	mockService.On("DeleteAccount", userID, "password123", "No longer needed").Return(nil)

	body, _ := json.Marshal(reqBody)
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("DELETE", "/api/v1/users/me", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)
}

func TestUserHandler_DeleteAccount_InvalidConfirmText(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	reqBody := map[string]interface{}{
		"password":     "password123",
		"reason":       "No longer needed",
		"confirm_text": "WRONG",
	}

	body, _ := json.Marshal(reqBody)
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("DELETE", "/api/v1/users/me", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	router.ServeHTTP(w, req)

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

func TestUserHandler_ExportData_Success(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	exportData := []byte(`{"user_id":"` + userID.String() + `","profile":{}}`)

	mockDataExportService.On("ExportUserDataAsJSON", mock.Anything, userID).Return(exportData, nil)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/v1/users/me/export", nil)
	req.Header.Set("X-User-ID", userID.String())
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Contains(t, w.Header().Get("Content-Type"), "application/json")
	assert.Contains(t, w.Header().Get("Content-Disposition"), "attachment")
	mockDataExportService.AssertExpectations(t)
}

func TestUserHandler_GetAccountStatus_Success(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	expectedStatus := &AccountStatus{
		UserID:     userID,
		Status:     "active",
		IsActive:   true,
		IsVerified: true,
		CreatedAt:  time.Now(),
	}

	mockService.On("GetAccountStatus", userID).Return(expectedStatus, nil)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/v1/users/me/status", nil)
	req.Header.Set("X-User-ID", userID.String())
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)

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

func TestUserHandler_RecoverAccount_Success(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	reqBody := map[string]interface{}{
		"email":    "test@example.com",
		"password": "password123",
	}

	mockService.On("RecoverAccount", "test@example.com", "password123").Return(nil)

	body, _ := json.Marshal(reqBody)
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("POST", "/api/v1/users/recover", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)
}

func TestUserHandler_RequestDataDeletion_Success(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	reqBody := map[string]interface{}{
		"password": "password123",
		"reason":   "GDPR",
	}

	mockService.On("RequestDataDeletion", userID, "password123", "GDPR").Return(nil)

	body, _ := json.Marshal(reqBody)
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("POST", "/api/v1/users/me/delete-request", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)
}

func TestUserHandler_GetUsersExceptMe_Success(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	otherUserID := uuid.New()

	users := []UserResponse{
		{ID: userID, Username: "me"},
		{ID: otherUserID, Username: "other"},
	}

	// Mock returns both users
	mockService.On("GetUsers", 1, 20, "").Return(users, 2, nil)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/v1/users/except-me", nil)
	req.Header.Set("X-User-ID", userID.String())
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)

	// Verify data only contains "other" user
	// response["data"] is the payload map (gin.H)
	payload := response["data"].(map[string]interface{})
	// payload["data"] is the users list
	data := payload["data"].([]interface{})
	assert.Len(t, data, 1)
	assert.Equal(t, "other", data[0].(map[string]interface{})["username"])
}

func TestUserHandler_UpdatePreferences_Success(t *testing.T) {
	mockService := new(MockUserService)
	mockDataExportService := new(MockDataExportService)
	router := setupTestUserRouter(mockService, mockDataExportService)

	userID := uuid.New()
	theme := "light"
	language := "fr"
	reqPreference := UserPreferencesRequest{
		Theme:    &theme,
		Language: &language,
	}

	expectedResponse := &UserPreferencesResponse{
		UserID:   userID,
		Theme:    "light",
		Language: "fr",
	}

	mockService.On("UpdateUserPreferences", userID, reqPreference).Return(expectedResponse, nil)

	body, _ := json.Marshal(reqPreference)
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("PATCH", "/api/v1/users/me/preferences", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)
}
