package services

import (
	"bytes"
	"image"
	"image/color"
	"image/jpeg"
	"mime/multipart"
	"os"
	"path/filepath"
	"testing"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func setupTestImageService(t *testing.T) (*ImageService, string, func()) {
	tempDir := t.TempDir()
	service := NewImageService(tempDir)
	cleanup := func() {
		os.RemoveAll(tempDir)
	}
	return service, tempDir, cleanup
}

func createTestImage(width, height int) image.Image {
	img := image.NewRGBA(image.Rect(0, 0, width, height))
	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			img.Set(x, y, color.RGBA{uint8(x % 256), uint8(y % 256), 128, 255})
		}
	}
	return img
}

func TestImageService_NewImageService(t *testing.T) {
	service := NewImageService("/test/dir")
	assert.NotNil(t, service)
	assert.Equal(t, "/test/dir", service.uploadDir)
}

func TestImageService_NewImageService_DefaultDir(t *testing.T) {
	service := NewImageService("")
	assert.NotNil(t, service)
	assert.Equal(t, "uploads/avatars", service.uploadDir)
}

func TestImageService_ValidateImage_Success(t *testing.T) {
	service, _, cleanup := setupTestImageService(t)
	defer cleanup()

	header := make(map[string][]string)
	header["Content-Type"] = []string{"image/jpeg"}
	fileHeader := &multipart.FileHeader{
		Filename: "test.jpg",
		Size:     1024 * 1024,
		Header:   header,
	}

	err := service.ValidateImage(fileHeader)
	assert.NoError(t, err)
}

func TestImageService_ValidateImage_PNG(t *testing.T) {
	service, _, cleanup := setupTestImageService(t)
	defer cleanup()

	header := make(map[string][]string)
	header["Content-Type"] = []string{"image/png"}
	fileHeader := &multipart.FileHeader{
		Filename: "test.png",
		Size:     1024 * 1024,
		Header:   header,
	}

	err := service.ValidateImage(fileHeader)
	assert.NoError(t, err)
}

func TestImageService_ValidateImage_WebP(t *testing.T) {
	service, _, cleanup := setupTestImageService(t)
	defer cleanup()

	header := make(map[string][]string)
	header["Content-Type"] = []string{"image/webp"}
	fileHeader := &multipart.FileHeader{
		Filename: "test.webp",
		Size:     1024 * 1024,
		Header:   header,
	}

	err := service.ValidateImage(fileHeader)
	assert.NoError(t, err)
}

func TestImageService_ValidateImage_TooLarge(t *testing.T) {
	service, _, cleanup := setupTestImageService(t)
	defer cleanup()

	header := make(map[string][]string)
	header["Content-Type"] = []string{"image/jpeg"}
	fileHeader := &multipart.FileHeader{
		Filename: "test.jpg",
		Size:     6 * 1024 * 1024,
		Header:   header,
	}

	err := service.ValidateImage(fileHeader)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "5MB limit")
}

func TestImageService_ValidateImage_InvalidFormat(t *testing.T) {
	service, _, cleanup := setupTestImageService(t)
	defer cleanup()

	header := make(map[string][]string)
	header["Content-Type"] = []string{"image/gif"}
	fileHeader := &multipart.FileHeader{
		Filename: "test.gif",
		Size:     1024 * 1024,
		Header:   header,
	}

	err := service.ValidateImage(fileHeader)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "unsupported image format")
}

func TestImageService_ResizeImage_Square(t *testing.T) {
	service, _, cleanup := setupTestImageService(t)
	defer cleanup()

	img := createTestImage(400, 400)
	resized := service.ResizeImage(img, 200, 200)

	bounds := resized.Bounds()
	assert.Equal(t, 200, bounds.Dx())
	assert.Equal(t, 200, bounds.Dy())
}

func TestImageService_ResizeImage_Wide(t *testing.T) {
	service, _, cleanup := setupTestImageService(t)
	defer cleanup()

	img := createTestImage(800, 400)              // 2:1 ratio
	resized := service.ResizeImage(img, 200, 200) // Target 1:1

	bounds := resized.Bounds()
	assert.Equal(t, 200, bounds.Dx())
	assert.Equal(t, 200, bounds.Dy())
}

func TestImageService_ResizeImage_Tall(t *testing.T) {
	service, _, cleanup := setupTestImageService(t)
	defer cleanup()

	img := createTestImage(400, 800)              // 1:2 ratio
	resized := service.ResizeImage(img, 200, 200) // Target 1:1

	bounds := resized.Bounds()
	assert.Equal(t, 200, bounds.Dx())
	assert.Equal(t, 200, bounds.Dy())
}

func TestImageService_EncodeJPEG_Success(t *testing.T) {
	service, _, cleanup := setupTestImageService(t)
	defer cleanup()

	img := createTestImage(200, 200)
	data, err := service.EncodeJPEG(img)
	_ = service // Use service

	assert.NoError(t, err)
	assert.NotNil(t, data)
	assert.Greater(t, len(data), 0)

	// Verify it's valid JPEG
	_, err = jpeg.Decode(bytes.NewReader(data))
	assert.NoError(t, err)
}

func TestImageService_ProcessAvatar_Success(t *testing.T) {
	t.Skip("Skipping ProcessAvatar test - requires real multipart.FileHeader with Open() method")
	// This test would require creating a real multipart.FileHeader with a working Open() method
	// which is complex to mock. The functionality is tested indirectly through ValidateImage,
	// ResizeImage, and EncodeJPEG tests.
}

func TestImageService_ProcessAvatar_InvalidFormat(t *testing.T) {
	t.Skip("Skipping ProcessAvatar test - requires custom multipart.FileHeader wrapper")
}

func TestImageService_ProcessAvatar_TooLarge(t *testing.T) {
	t.Skip("Skipping ProcessAvatar test - requires custom multipart.FileHeader wrapper")
}

func TestImageService_UploadToS3_Success(t *testing.T) {
	service, tempDir, cleanup := setupTestImageService(t)
	defer cleanup()

	data := []byte("test image data")
	key := "test-key.jpg"

	url, err := service.UploadToS3(data, key)
	assert.NoError(t, err)
	assert.Contains(t, url, "test-key.jpg")

	// Verify file was created
	filePath := filepath.Join(tempDir, "test-key.jpg")
	_, err = os.Stat(filePath)
	assert.NoError(t, err)

	// Verify file content
	fileData, err := os.ReadFile(filePath)
	assert.NoError(t, err)
	assert.Equal(t, data, fileData)
}

func TestImageService_UploadToS3_CreatesDirectory(t *testing.T) {
	service, tempDir, cleanup := setupTestImageService(t)
	defer cleanup()

	// Use a subdirectory that doesn't exist
	service.uploadDir = filepath.Join(tempDir, "new", "subdir")

	data := []byte("test image data")
	key := "test-key.jpg"

	_, err := service.UploadToS3(data, key)
	assert.NoError(t, err)

	// Verify directory was created
	_, err = os.Stat(service.uploadDir)
	assert.NoError(t, err)
}

func TestImageService_DeleteFromS3_Success(t *testing.T) {
	service, tempDir, cleanup := setupTestImageService(t)
	defer cleanup()

	// Create a file first
	key := "test-key.jpg"
	data := []byte("test image data")
	_, err := service.UploadToS3(data, key)
	require.NoError(t, err)

	// Delete it
	url := "/uploads/avatars/test-key.jpg"
	err = service.DeleteFromS3(url)
	assert.NoError(t, err)

	// Verify file was deleted
	filePath := filepath.Join(tempDir, "test-key.jpg")
	_, err = os.Stat(filePath)
	assert.Error(t, err)
	assert.True(t, os.IsNotExist(err))
}

func TestImageService_DeleteFromS3_NotExists(t *testing.T) {
	service, _, cleanup := setupTestImageService(t)
	defer cleanup()

	url := "/uploads/avatars/non-existent.jpg"
	err := service.DeleteFromS3(url)
	assert.NoError(t, err) // Should not error if file doesn't exist
}

func TestImageService_GenerateS3Key(t *testing.T) {
	service, _, cleanup := setupTestImageService(t)
	defer cleanup()

	userID := uuid.New()
	key := service.GenerateS3Key(userID)

	assert.Contains(t, key, "avatars")
	assert.Contains(t, key, userID.String())
	assert.Contains(t, key, ".jpg")
}

func TestImageService_Constants(t *testing.T) {
	assert.Equal(t, 5*1024*1024, MaxAvatarSize)
	assert.Equal(t, 200, AvatarWidth)
	assert.Equal(t, 200, AvatarHeight)
	assert.Equal(t, 90, JPEGQuality)
}
