package services

import (
	"bytes"
	"context"
	"fmt"
	"image"
	"image/jpeg"
	"image/png"
	"mime/multipart"

	"github.com/disintegration/imaging"
	"go.uber.org/zap"
)

// ImageSize represents predefined image sizes
type ImageSize string

const (
	SizeThumbnail ImageSize = "thumbnail" // 100x100
	SizeSmall     ImageSize = "small"     // 200x200 (avatar default)
	SizeMedium    ImageSize = "medium"    // 400x400
	SizeLarge     ImageSize = "large"     // 800x800
)

// ImageFormat represents output image format
type ImageFormat string

const (
	FormatJPEG ImageFormat = "jpeg"
	FormatPNG  ImageFormat = "png"
	FormatWebP ImageFormat = "webp"
)

// ImageProcessingOptions represents options for image processing
type ImageProcessingOptions struct {
	Width               int         // Target width (0 = use size preset)
	Height              int         // Target height (0 = use size preset)
	Size                ImageSize   // Preset size (overrides width/height if set)
	Format              ImageFormat // Output format (default: JPEG)
	Quality             int         // JPEG/WebP quality (1-100, default: 85)
	MaintainAspectRatio bool        // Maintain aspect ratio (default: true)
	CropCenter          bool        // Crop center to fit dimensions (default: true)
}

// ProcessedImageResult represents the result of image processing
type ProcessedImageResult struct {
	Data     []byte      `json:"data"`
	Format   ImageFormat `json:"format"`
	Width    int         `json:"width"`
	Height   int         `json:"height"`
	Size     int64       `json:"size"` // Size in bytes
	MimeType string      `json:"mime_type"`
}

// EnhancedImageService provides enhanced image processing capabilities
// BE-SVC-010: Implement image processing service
type EnhancedImageService struct {
	logger *zap.Logger
}

// NewEnhancedImageService creates a new enhanced image service
func NewEnhancedImageService(logger *zap.Logger) *EnhancedImageService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &EnhancedImageService{
		logger: logger,
	}
}

// GetSizeDimensions returns width and height for a preset size
func GetSizeDimensions(size ImageSize) (width, height int) {
	switch size {
	case SizeThumbnail:
		return 100, 100
	case SizeSmall:
		return 200, 200
	case SizeMedium:
		return 400, 400
	case SizeLarge:
		return 800, 800
	default:
		return 200, 200 // Default to small
	}
}

// ProcessImage processes an image with the given options
func (s *EnhancedImageService) ProcessImage(
	ctx context.Context,
	fileHeader *multipart.FileHeader,
	options ImageProcessingOptions,
) (*ProcessedImageResult, error) {
	// Open file
	file, err := fileHeader.Open()
	if err != nil {
		return nil, fmt.Errorf("failed to open file: %w", err)
	}
	defer file.Close()

	// Decode image
	img, _, err := image.Decode(file)
	if err != nil {
		return nil, fmt.Errorf("invalid image format: %w", err)
	}

	// Determine target dimensions
	width := options.Width
	height := options.Height
	if options.Size != "" {
		width, height = GetSizeDimensions(options.Size)
	}
	if width == 0 {
		width = 200 // Default
	}
	if height == 0 {
		height = 200 // Default
	}

	// Process image
	var processed image.Image
	if options.CropCenter {
		processed = s.resizeWithCropCenter(img, width, height)
	} else if options.MaintainAspectRatio {
		processed = s.resizeMaintainAspectRatio(img, width, height)
	} else {
		processed = imaging.Resize(img, width, height, imaging.Lanczos)
	}

	// Determine output format
	outputFormat := options.Format
	if outputFormat == "" {
		outputFormat = FormatJPEG // Default
	}

	// Set quality defaults
	quality := options.Quality
	if quality <= 0 || quality > 100 {
		quality = 85 // Default quality
	}

	// Encode image
	var data []byte
	var mimeType string
	var errEncode error

	switch outputFormat {
	case FormatJPEG:
		data, errEncode = s.encodeJPEG(processed, quality)
		mimeType = "image/jpeg"
	case FormatPNG:
		data, errEncode = s.encodePNG(processed)
		mimeType = "image/png"
	case FormatWebP:
		// WebP encoding would require additional library
		// For now, fallback to JPEG
		s.logger.Warn("WebP format not fully supported, falling back to JPEG")
		data, errEncode = s.encodeJPEG(processed, quality)
		mimeType = "image/jpeg"
	default:
		data, errEncode = s.encodeJPEG(processed, quality)
		mimeType = "image/jpeg"
	}

	if errEncode != nil {
		return nil, fmt.Errorf("failed to encode image: %w", errEncode)
	}

	bounds := processed.Bounds()
	return &ProcessedImageResult{
		Data:     data,
		Format:   outputFormat,
		Width:    bounds.Dx(),
		Height:   bounds.Dy(),
		Size:     int64(len(data)),
		MimeType: mimeType,
	}, nil
}

// ProcessAvatar processes an avatar image with optimized settings
func (s *EnhancedImageService) ProcessAvatar(
	ctx context.Context,
	fileHeader *multipart.FileHeader,
) (*ProcessedImageResult, error) {
	options := ImageProcessingOptions{
		Size:                SizeSmall,
		Format:              FormatJPEG,
		Quality:             85, // Good balance between quality and file size
		MaintainAspectRatio: true,
		CropCenter:          true,
	}

	return s.ProcessImage(ctx, fileHeader, options)
}

// ProcessImageMultipleSizes processes an image and generates multiple sizes
func (s *EnhancedImageService) ProcessImageMultipleSizes(
	ctx context.Context,
	fileHeader *multipart.FileHeader,
	sizes []ImageSize,
	format ImageFormat,
	quality int,
) (map[ImageSize]*ProcessedImageResult, error) {
	results := make(map[ImageSize]*ProcessedImageResult)

	for _, size := range sizes {
		options := ImageProcessingOptions{
			Size:                size,
			Format:              format,
			Quality:             quality,
			MaintainAspectRatio: true,
			CropCenter:          true,
		}

		result, err := s.ProcessImage(ctx, fileHeader, options)
		if err != nil {
			s.logger.Warn("Failed to process image size",
				zap.String("size", string(size)),
				zap.Error(err),
			)
			continue
		}

		results[size] = result
	}

	return results, nil
}

// OptimizeImage optimizes an image by reducing quality while maintaining visual quality
func (s *EnhancedImageService) OptimizeImage(
	ctx context.Context,
	img image.Image,
	targetSizeKB int64,
	format ImageFormat,
) ([]byte, error) {
	// Start with high quality and reduce until target size is reached
	quality := 90
	maxIterations := 10
	targetSizeBytes := targetSizeKB * 1024

	for i := 0; i < maxIterations; i++ {
		var data []byte
		var err error

		switch format {
		case FormatJPEG:
			data, err = s.encodeJPEG(img, quality)
		case FormatPNG:
			data, err = s.encodePNG(img)
		default:
			data, err = s.encodeJPEG(img, quality)
		}

		if err != nil {
			return nil, fmt.Errorf("failed to encode image: %w", err)
		}

		if int64(len(data)) <= targetSizeBytes || quality <= 50 {
			return data, nil
		}

		quality -= 10
	}

	// Return last attempt even if target not reached
	return s.encodeJPEG(img, 50)
}

// resizeWithCropCenter resizes an image with crop center to fit exact dimensions
func (s *EnhancedImageService) resizeWithCropCenter(img image.Image, width, height int) image.Image {
	bounds := img.Bounds()
	imgWidth := bounds.Dx()
	imgHeight := bounds.Dy()

	// Calculate ratio to maintain aspect ratio
	ratio := float64(imgWidth) / float64(imgHeight)
	targetRatio := float64(width) / float64(height)

	var cropWidth, cropHeight int
	if ratio > targetRatio {
		// Image is wider, crop width
		cropHeight = imgHeight
		cropWidth = int(float64(cropHeight) * targetRatio)
	} else {
		// Image is taller, crop height
		cropWidth = imgWidth
		cropHeight = int(float64(cropWidth) / targetRatio)
	}

	// Crop center
	cropX := (imgWidth - cropWidth) / 2
	cropY := (imgHeight - cropHeight) / 2
	cropped := imaging.Crop(img, image.Rect(cropX, cropY, cropX+cropWidth, cropY+cropHeight))

	// Final resize
	return imaging.Resize(cropped, width, height, imaging.Lanczos)
}

// resizeMaintainAspectRatio resizes an image maintaining aspect ratio
func (s *EnhancedImageService) resizeMaintainAspectRatio(img image.Image, maxWidth, maxHeight int) image.Image {
	bounds := img.Bounds()
	imgWidth := bounds.Dx()
	imgHeight := bounds.Dy()

	// Calculate scaling factor
	scaleX := float64(maxWidth) / float64(imgWidth)
	scaleY := float64(maxHeight) / float64(imgHeight)
	scale := scaleX
	if scaleY < scaleX {
		scale = scaleY
	}

	newWidth := int(float64(imgWidth) * scale)
	newHeight := int(float64(imgHeight) * scale)

	return imaging.Resize(img, newWidth, newHeight, imaging.Lanczos)
}

// encodeJPEG encodes an image as JPEG
func (s *EnhancedImageService) encodeJPEG(img image.Image, quality int) ([]byte, error) {
	var buf bytes.Buffer
	if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality}); err != nil {
		return nil, fmt.Errorf("failed to encode JPEG: %w", err)
	}
	return buf.Bytes(), nil
}

// encodePNG encodes an image as PNG
func (s *EnhancedImageService) encodePNG(img image.Image) ([]byte, error) {
	var buf bytes.Buffer
	encoder := png.Encoder{CompressionLevel: png.BestCompression}
	if err := encoder.Encode(&buf, img); err != nil {
		return nil, fmt.Errorf("failed to encode PNG: %w", err)
	}
	return buf.Bytes(), nil
}

// ValidateImageFile validates an image file
func (s *EnhancedImageService) ValidateImageFile(
	fileHeader *multipart.FileHeader,
	maxSize int64,
) error {
	// Validate file size
	if maxSize > 0 && fileHeader.Size > maxSize {
		return fmt.Errorf("file size exceeds %d bytes limit", maxSize)
	}

	// Validate MIME type
	contentType := fileHeader.Header.Get("Content-Type")
	allowedTypes := []string{"image/jpeg", "image/png", "image/webp", "image/gif"}
	valid := false
	for _, allowedType := range allowedTypes {
		if contentType == allowedType {
			valid = true
			break
		}
	}
	if !valid {
		return fmt.Errorf("unsupported image format. Allowed: JPEG, PNG, WebP, GIF")
	}

	// Try to decode to verify it's a valid image
	file, err := fileHeader.Open()
	if err != nil {
		return fmt.Errorf("failed to open file: %w", err)
	}
	defer file.Close()

	_, _, err = image.Decode(file)
	if err != nil {
		return fmt.Errorf("invalid image file: %w", err)
	}

	return nil
}

// GetImageDimensions returns the dimensions of an image file
func (s *EnhancedImageService) GetImageDimensions(
	fileHeader *multipart.FileHeader,
) (width, height int, err error) {
	file, err := fileHeader.Open()
	if err != nil {
		return 0, 0, fmt.Errorf("failed to open file: %w", err)
	}
	defer file.Close()

	img, _, err := image.Decode(file)
	if err != nil {
		return 0, 0, fmt.Errorf("failed to decode image: %w", err)
	}

	bounds := img.Bounds()
	return bounds.Dx(), bounds.Dy(), nil
}

// ConvertImageFormat converts an image from one format to another
func (s *EnhancedImageService) ConvertImageFormat(
	ctx context.Context,
	sourceData []byte,
	sourceFormat string,
	targetFormat ImageFormat,
	quality int,
) ([]byte, error) {
	// Decode source image
	img, _, err := image.Decode(bytes.NewReader(sourceData))
	if err != nil {
		return nil, fmt.Errorf("failed to decode source image: %w", err)
	}

	// Encode to target format
	switch targetFormat {
	case FormatJPEG:
		if quality <= 0 {
			quality = 85
		}
		return s.encodeJPEG(img, quality)
	case FormatPNG:
		return s.encodePNG(img)
	default:
		return s.encodeJPEG(img, quality)
	}
}
