package handlers

import (
	"bytes"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"testing"

	"veza-backend-api/internal/common"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"

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

// MockImageService implements ImageService interface for testing
type MockImageService struct {
	mock.Mock
}

func (m *MockImageService) ProcessAvatar(fileHeader *multipart.FileHeader) ([]byte, error) {
	args := m.Called(fileHeader)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]byte), args.Error(1)
}

func (m *MockImageService) GenerateS3Key(userID uuid.UUID) string {
	args := m.Called(userID)
	return args.String(0)
}

func (m *MockImageService) UploadToS3(data []byte, key string) (string, error) {
	args := m.Called(data, key)
	return args.String(0), args.Error(1)
}

func (m *MockImageService) DeleteFromS3(avatarURL string) error {
	args := m.Called(avatarURL)
	return args.Error(0)
}

// MockUserService implements UserService interface for testing
type MockUserServiceForAvatar struct {
	mock.Mock
}

func (m *MockUserServiceForAvatar) GetByID(userID uuid.UUID) (*models.User, error) {
	args := m.Called(userID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.User), args.Error(1)
}

func (m *MockUserServiceForAvatar) UpdateAvatarURL(userID uuid.UUID, avatarURL string) error {
	args := m.Called(userID, avatarURL)
	return args.Error(0)
}

func setupTestAvatarRouter(mockImageService *MockImageService, mockUserService *MockUserServiceForAvatar) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	handler := NewAvatarHandlerWithInterface(mockImageService, mockUserService)

	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.POST("/:userId/avatar", handler.UploadAvatar)
			users.DELETE("/:userId/avatar", handler.DeleteAvatar)
		}
	}

	return router
}

func createMultipartFormData(t *testing.T, fieldName, filename string, content []byte) (*bytes.Buffer, string) {
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	part, err := writer.CreateFormFile(fieldName, filename)
	assert.NoError(t, err)

	_, err = part.Write(content)
	assert.NoError(t, err)

	err = writer.Close()
	assert.NoError(t, err)

	return body, writer.FormDataContentType()
}

func TestAvatarHandler_UploadAvatar_Success(t *testing.T) {
	// Setup
	mockImageService := new(MockImageService)
	mockUserService := new(MockUserServiceForAvatar)
	router := setupTestAvatarRouter(mockImageService, mockUserService)

	userID := uuid.New()
	imageData := []byte("fake image data")
	s3Key := "avatars/" + userID.String() + ".jpg"
	avatarURL := "https://s3.example.com/" + s3Key

	body, contentType := createMultipartFormData(t, "avatar", "avatar.jpg", imageData)

	// Mock expectations
	mockImageService.On("ProcessAvatar", mock.AnythingOfType("*multipart.FileHeader")).Return(imageData, nil)
	mockImageService.On("GenerateS3Key", userID).Return(s3Key)
	mockImageService.On("UploadToS3", imageData, s3Key).Return(avatarURL, nil)
	mockUserService.On("UpdateAvatarURL", userID, avatarURL).Return(nil)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/avatar", body)
	req.Header.Set("Content-Type", contentType)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	mockImageService.AssertExpectations(t)
	mockUserService.AssertExpectations(t)
}

func TestAvatarHandler_UploadAvatar_Unauthorized(t *testing.T) {
	// Setup
	mockImageService := new(MockImageService)
	mockUserService := new(MockUserServiceForAvatar)
	router := setupTestAvatarRouter(mockImageService, mockUserService)

	userID := uuid.New()
	imageData := []byte("fake image data")
	body, contentType := createMultipartFormData(t, "avatar", "avatar.jpg", imageData)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/avatar", body)
	req.Header.Set("Content-Type", contentType)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusForbidden)
	mockImageService.AssertNotCalled(t, "ProcessAvatar")
	mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
}

func TestAvatarHandler_UploadAvatar_InvalidUserID(t *testing.T) {
	// Setup
	mockImageService := new(MockImageService)
	mockUserService := new(MockUserServiceForAvatar)
	router := setupTestAvatarRouter(mockImageService, mockUserService)

	userID := uuid.New()
	imageData := []byte("fake image data")
	body, contentType := createMultipartFormData(t, "avatar", "avatar.jpg", imageData)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/users/invalid/avatar", body)
	req.Header.Set("Content-Type", contentType)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockImageService.AssertNotCalled(t, "ProcessAvatar")
	mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
}

func TestAvatarHandler_UploadAvatar_Forbidden(t *testing.T) {
	// Setup
	mockImageService := new(MockImageService)
	mockUserService := new(MockUserServiceForAvatar)
	router := setupTestAvatarRouter(mockImageService, mockUserService)

	userID1 := uuid.New()
	userID2 := uuid.New()
	imageData := []byte("fake image data")
	body, contentType := createMultipartFormData(t, "avatar", "avatar.jpg", imageData)

	// Execute - userID1 trying to upload avatar for userID2
	req, _ := http.NewRequest("POST", "/api/v1/users/"+userID2.String()+"/avatar", body)
	req.Header.Set("Content-Type", contentType)
	req.Header.Set("X-User-ID", userID1.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusForbidden, w.Code)
	mockImageService.AssertNotCalled(t, "ProcessAvatar")
	mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
}

func TestAvatarHandler_UploadAvatar_NoFile(t *testing.T) {
	// Setup
	mockImageService := new(MockImageService)
	mockUserService := new(MockUserServiceForAvatar)
	router := setupTestAvatarRouter(mockImageService, mockUserService)

	userID := uuid.New()

	// Execute - no file in request
	req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/avatar", bytes.NewBuffer([]byte{}))
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockImageService.AssertNotCalled(t, "ProcessAvatar")
	mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
}

func TestAvatarHandler_UploadAvatar_ProcessAvatarError(t *testing.T) {
	// Setup
	mockImageService := new(MockImageService)
	mockUserService := new(MockUserServiceForAvatar)
	router := setupTestAvatarRouter(mockImageService, mockUserService)

	userID := uuid.New()
	imageData := []byte("fake image data")
	body, contentType := createMultipartFormData(t, "avatar", "avatar.jpg", imageData)

	// Mock expectations - ProcessAvatar returns error
	mockImageService.On("ProcessAvatar", mock.AnythingOfType("*multipart.FileHeader")).Return(nil, assert.AnError)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/avatar", body)
	req.Header.Set("Content-Type", contentType)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockImageService.AssertExpectations(t)
	mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
}

func TestAvatarHandler_UploadAvatar_UploadToS3Error(t *testing.T) {
	// Setup
	mockImageService := new(MockImageService)
	mockUserService := new(MockUserServiceForAvatar)
	router := setupTestAvatarRouter(mockImageService, mockUserService)

	userID := uuid.New()
	imageData := []byte("fake image data")
	s3Key := "avatars/" + userID.String() + ".jpg"
	body, contentType := createMultipartFormData(t, "avatar", "avatar.jpg", imageData)

	// Mock expectations - UploadToS3 returns error
	mockImageService.On("ProcessAvatar", mock.AnythingOfType("*multipart.FileHeader")).Return(imageData, nil)
	mockImageService.On("GenerateS3Key", userID).Return(s3Key)
	mockImageService.On("UploadToS3", imageData, s3Key).Return("", assert.AnError)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/avatar", body)
	req.Header.Set("Content-Type", contentType)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusInternalServerError, w.Code)
	mockImageService.AssertExpectations(t)
	mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
}

func TestAvatarHandler_DeleteAvatar_Success(t *testing.T) {
	// Setup
	mockImageService := new(MockImageService)
	mockUserService := new(MockUserServiceForAvatar)
	router := setupTestAvatarRouter(mockImageService, mockUserService)

	userID := uuid.New()
	avatarURL := "https://s3.example.com/avatars/" + userID.String() + ".jpg"

	user := &models.User{
		ID:     userID,
		Avatar: avatarURL,
	}

	// Mock expectations
	mockUserService.On("GetByID", userID).Return(user, nil)
	mockImageService.On("DeleteFromS3", avatarURL).Return(nil)
	mockUserService.On("UpdateAvatarURL", userID, "").Return(nil)

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

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	mockImageService.AssertExpectations(t)
	mockUserService.AssertExpectations(t)
}

func TestAvatarHandler_DeleteAvatar_Unauthorized(t *testing.T) {
	// Setup
	mockImageService := new(MockImageService)
	mockUserService := new(MockUserServiceForAvatar)
	router := setupTestAvatarRouter(mockImageService, mockUserService)

	userID := uuid.New()

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

	// Assert
	assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusForbidden)
	mockImageService.AssertNotCalled(t, "DeleteFromS3")
	mockUserService.AssertNotCalled(t, "GetByID")
	mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
}

func TestAvatarHandler_DeleteAvatar_InvalidUserID(t *testing.T) {
	// Setup
	mockImageService := new(MockImageService)
	mockUserService := new(MockUserServiceForAvatar)
	router := setupTestAvatarRouter(mockImageService, mockUserService)

	userID := uuid.New()

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

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockImageService.AssertNotCalled(t, "DeleteFromS3")
	mockUserService.AssertNotCalled(t, "GetByID")
	mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
}

func TestAvatarHandler_DeleteAvatar_Forbidden(t *testing.T) {
	// Setup
	mockImageService := new(MockImageService)
	mockUserService := new(MockUserServiceForAvatar)
	router := setupTestAvatarRouter(mockImageService, mockUserService)

	userID1 := uuid.New()
	userID2 := uuid.New()

	// Execute - userID1 trying to delete avatar for userID2
	req, _ := http.NewRequest("DELETE", "/api/v1/users/"+userID2.String()+"/avatar", nil)
	req.Header.Set("X-User-ID", userID1.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusForbidden, w.Code)
	mockImageService.AssertNotCalled(t, "DeleteFromS3")
	mockUserService.AssertNotCalled(t, "GetByID")
	mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
}

func TestAvatarHandler_DeleteAvatar_UserNotFound(t *testing.T) {
	// Setup
	mockImageService := new(MockImageService)
	mockUserService := new(MockUserServiceForAvatar)
	router := setupTestAvatarRouter(mockImageService, mockUserService)

	userID := uuid.New()

	// Mock expectations - GetByID returns error
	mockUserService.On("GetByID", userID).Return(nil, assert.AnError)

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

	// Assert
	assert.Equal(t, http.StatusNotFound, w.Code)
	mockUserService.AssertExpectations(t)
	mockImageService.AssertNotCalled(t, "DeleteFromS3")
	mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
}

func TestAvatarHandler_DeleteAvatar_NoAvatar(t *testing.T) {
	// Setup
	mockImageService := new(MockImageService)
	mockUserService := new(MockUserServiceForAvatar)
	router := setupTestAvatarRouter(mockImageService, mockUserService)

	userID := uuid.New()

	user := &models.User{
		ID:     userID,
		Avatar: "", // No avatar
	}

	// Mock expectations
	mockUserService.On("GetByID", userID).Return(user, nil)
	mockUserService.On("UpdateAvatarURL", userID, "").Return(nil)

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

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	mockUserService.AssertExpectations(t)
	// DeleteFromS3 should not be called if avatar is empty
	mockImageService.AssertNotCalled(t, "DeleteFromS3")
}

func TestAvatarHandler_DeleteAvatar_UpdateAvatarURLError(t *testing.T) {
	// Setup
	mockImageService := new(MockImageService)
	mockUserService := new(MockUserServiceForAvatar)
	router := setupTestAvatarRouter(mockImageService, mockUserService)

	userID := uuid.New()
	avatarURL := "https://s3.example.com/avatars/" + userID.String() + ".jpg"

	user := &models.User{
		ID:     userID,
		Avatar: avatarURL,
	}

	// Mock expectations - UpdateAvatarURL returns error
	mockUserService.On("GetByID", userID).Return(user, nil)
	mockImageService.On("DeleteFromS3", avatarURL).Return(nil)
	mockUserService.On("UpdateAvatarURL", userID, "").Return(assert.AnError)

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

	// Assert
	assert.Equal(t, http.StatusInternalServerError, w.Code)
	mockImageService.AssertExpectations(t)
	mockUserService.AssertExpectations(t)
}

func TestNewAvatarHandler(t *testing.T) {
	// Setup
	mockImageService := &services.ImageService{}
	mockUserService := &services.UserService{}

	// Execute
	handler := NewAvatarHandler(mockImageService, mockUserService)

	// Assert
	assert.NotNil(t, handler)
	assert.NotNil(t, handler.imageService)
	assert.NotNil(t, handler.userService)
}
