package utils

import (
	"fmt"
	"image"
	"image/jpeg"
	"image/png"
	"mime/multipart"
	"os"
	"path/filepath"
	"strings"
	"time"

	"gauth-central/config"

	"github.com/chai2010/webp"
	"github.com/disintegration/imaging"
)

type ImageOptions struct {
	Width   int
	Height  int
	Quality float32 // 1-100 (WebP uses float32)
	Format  string  // "webp", "jpg", "png"
	Mode    string  // "cover", "contain", "resize"
}

// SaveOptimizedImage processes and saves an image
// Returns the relative path to the saved file (e.g., "/uploads/avatars/filename.webp")
func SaveOptimizedImage(fileHeader *multipart.FileHeader, uploadDir string, userID string, opts *ImageOptions) (string, error) {
	// If opts is nil, use defaults from config
	if opts == nil {
		opts = &ImageOptions{
			Width:   config.AppConfig.AvatarWidth,
			Height:  config.AppConfig.AvatarHeight,
			Quality: float32(config.AppConfig.AvatarQuality),
			Format:  config.AppConfig.AvatarFormat,
			Mode:    config.AppConfig.AvatarMode,
		}
	}

	// Open the file
	srcFile, err := fileHeader.Open()
	if err != nil {
		return "", fmt.Errorf("failed to open file: %v", err)
	}
	defer srcFile.Close()

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

	// Resize logic
	if opts.Width > 0 || opts.Height > 0 {
		switch strings.ToLower(opts.Mode) {
		case "cover":
			// Fill requires both dimensions to be effective for cropping.
			// If one is missing, we fall back to Resize which preserves aspect ratio.
			if opts.Width > 0 && opts.Height > 0 {
				img = imaging.Fill(img, opts.Width, opts.Height, imaging.Center, imaging.Lanczos)
			} else {
				img = imaging.Resize(img, opts.Width, opts.Height, imaging.Lanczos)
			}
		case "contain":
			// Fit fits the image within the box, preserving aspect ratio.
			// If one dimension is 0, Fit might behave like Resize(w, h) if implementation allows,
			// but imaging.Fit usually expects a box.
			// If one is 0, we assume the user wants to limit the other dimension.
			if opts.Width > 0 && opts.Height > 0 {
				img = imaging.Fit(img, opts.Width, opts.Height, imaging.Lanczos)
			} else {
				img = imaging.Resize(img, opts.Width, opts.Height, imaging.Lanczos)
			}
		default:
			// "resize" or empty: Resize preserves aspect ratio if one arg is 0.
			// If both are provided, it stretches unless we use Fill/Fit.
			// imaging.Resize stretches if both are non-zero.
			img = imaging.Resize(img, opts.Width, opts.Height, imaging.Lanczos)
		}
	}

	// Determine output format and filename
	ext := "." + strings.ToLower(opts.Format)
	if opts.Format == "" {
		ext = ".webp" // Default to WebP
		opts.Format = "webp"
	}

	// Generate filename
	filename := fmt.Sprintf("%s_%d%s", userID, time.Now().UnixNano(), ext)

	// Ensure directory exists
	if err := os.MkdirAll(uploadDir, 0755); err != nil {
		return "", fmt.Errorf("failed to create directory: %v", err)
	}

	fullPath := filepath.Join(uploadDir, filename)
	outFile, err := os.Create(fullPath)
	if err != nil {
		return "", fmt.Errorf("failed to create output file: %v", err)
	}
	defer outFile.Close()

	// Encode
	switch strings.ToLower(opts.Format) {
	case "jpg", "jpeg":
		quality := int(opts.Quality)
		if quality < 1 || quality > 100 {
			quality = 90
		}
		err = jpeg.Encode(outFile, img, &jpeg.Options{Quality: quality})
	case "png":
		err = png.Encode(outFile, img) // PNG is lossless
	case "webp", "":
		err = webp.Encode(outFile, img, &webp.Options{Lossless: false, Quality: opts.Quality})
	default:
		// Fallback to WebP
		err = webp.Encode(outFile, img, &webp.Options{Lossless: false, Quality: opts.Quality})
	}

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

	// Return relative path
	relPath := filepath.Join(uploadDir, filename)
	if strings.HasPrefix(relPath, ".") {
		relPath = relPath[1:]
	}
	if !strings.HasPrefix(relPath, "/") {
		relPath = "/" + relPath
	}

	return relPath, nil
}
