package controllers

import (
	"encoding/json"
	"fmt"
	"image"
	"image/jpeg"
	"image/png"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/disintegration/imaging"
	"github.com/gin-gonic/gin"
)

type ImageProcessingController struct{}

// ImageProcessRequest represents the request body for image processing
type ImageProcessRequest struct {
	ImageURL    string  `json:"image_url"`    // URL of image to process
	Operation   string  `json:"operation"`    // crop, resize, rotate, flip, filter
	Width       int     `json:"width"`        // Target width (for resize)
	Height      int     `json:"height"`       // Target height (for resize)
	CropX       int     `json:"crop_x"`       // Crop coordinates
	CropY       int     `json:"crop_y"`
	CropWidth   int     `json:"crop_width"`
	CropHeight  int     `json:"crop_height"`
	Rotation    int     `json:"rotation"`     // Rotation angle (90, 180, 270)
	FlipH       bool    `json:"flip_h"`       // Flip horizontal
	FlipV       bool    `json:"flip_v"`       // Flip vertical
	Brightness  float64 `json:"brightness"`   // -100 to 100
	Contrast    float64 `json:"contrast"`     // -100 to 100
	Saturation  float64 `json:"saturation"`   // -100 to 100
	Blur        float64 `json:"blur"`         // 0 to 10
	Sharpen     float64 `json:"sharpen"`      // 0 to 10
	Grayscale   bool    `json:"grayscale"`    // Convert to grayscale
	Quality     int     `json:"quality"`      // JPEG quality 1-100
}

// ProcessImage handles image processing operations
func (ctrl *ImageProcessingController) ProcessImage(c *gin.Context) {
	var req ImageProcessRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: " + err.Error()})
		return
	}

	// Validate quality
	if req.Quality <= 0 || req.Quality > 100 {
		req.Quality = 85 // Default quality
	}

	// Load image
	img, format, err := ctrl.loadImage(req.ImageURL)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to load image: " + err.Error()})
		return
	}

	// Apply operations based on request
	processedImg := img

	// Crop
	if req.CropWidth > 0 && req.CropHeight > 0 {
		processedImg = imaging.Crop(processedImg, image.Rect(
			req.CropX,
			req.CropY,
			req.CropX+req.CropWidth,
			req.CropY+req.CropHeight,
		))
	}

	// Resize
	if req.Width > 0 || req.Height > 0 {
		width := req.Width
		height := req.Height
		if width == 0 {
			width = 0 // Auto width
		}
		if height == 0 {
			height = 0 // Auto height
		}
		processedImg = imaging.Resize(processedImg, width, height, imaging.Lanczos)
	}

	// Rotate
	if req.Rotation != 0 {
		switch req.Rotation % 360 {
		case 90, -270:
			processedImg = imaging.Rotate90(processedImg)
		case 180, -180:
			processedImg = imaging.Rotate180(processedImg)
		case 270, -90:
			processedImg = imaging.Rotate270(processedImg)
		}
	}

	// Flip
	if req.FlipH {
		processedImg = imaging.FlipH(processedImg)
	}
	if req.FlipV {
		processedImg = imaging.FlipV(processedImg)
	}

	// Brightness
	if req.Brightness != 0 {
		processedImg = imaging.AdjustBrightness(processedImg, req.Brightness)
	}

	// Contrast
	if req.Contrast != 0 {
		processedImg = imaging.AdjustContrast(processedImg, req.Contrast)
	}

	// Saturation
	if req.Saturation != 0 {
		processedImg = imaging.AdjustSaturation(processedImg, req.Saturation)
	}

	// Blur
	if req.Blur > 0 {
		processedImg = imaging.Blur(processedImg, req.Blur)
	}

	// Sharpen
	if req.Sharpen > 0 {
		processedImg = imaging.Sharpen(processedImg, req.Sharpen)
	}

	// Grayscale
	if req.Grayscale {
		processedImg = imaging.Grayscale(processedImg)
	}

	// Save processed image
	outputPath, err := ctrl.saveProcessedImage(processedImg, format, req.Quality)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save image: " + err.Error()})
		return
	}

	// Return the new URL
	c.JSON(http.StatusOK, gin.H{
		"url":    outputPath,
		"format": format,
	})
}

// loadImage loads an image from a URL or local path
func (ctrl *ImageProcessingController) loadImage(imageURL string) (image.Image, string, error) {
	// Check if it's a local file path
	if strings.HasPrefix(imageURL, "/uploads/") || strings.HasPrefix(imageURL, "uploads/") {
		// Local file
		localPath := filepath.Join(".", imageURL)
		file, err := os.Open(localPath)
		if err != nil {
			return nil, "", fmt.Errorf("failed to open local file: %w", err)
		}
		defer file.Close()

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

	// HTTP URL
	resp, err := http.Get(imageURL)
	if err != nil {
		return nil, "", fmt.Errorf("failed to fetch image: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, "", fmt.Errorf("failed to fetch image: status %d", resp.StatusCode)
	}

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

	return img, format, nil
}

// saveProcessedImage saves the processed image and returns the path
func (ctrl *ImageProcessingController) saveProcessedImage(img image.Image, format string, quality int) (string, error) {
	// Create uploads directory if it doesn't exist
	uploadsDir := "./uploads"
	if err := os.MkdirAll(uploadsDir, 0755); err != nil {
		return "", fmt.Errorf("failed to create uploads directory: %w", err)
	}

	// Generate unique filename
	timestamp := time.Now().UnixNano() / int64(time.Millisecond)
	filename := fmt.Sprintf("processed_%d.jpg", timestamp)
	outputPath := filepath.Join(uploadsDir, filename)

	// Create output file
	outFile, err := os.Create(outputPath)
	if err != nil {
		return "", fmt.Errorf("failed to create output file: %w", err)
	}
	defer outFile.Close()

	// Encode and save
	if format == "png" {
		err = png.Encode(outFile, img)
	} else {
		err = jpeg.Encode(outFile, img, &jpeg.Options{Quality: quality})
	}

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

	// Return relative URL
	return "/uploads/" + filename, nil
}

// CropAndUpload handles image cropping with upload
func (ctrl *ImageProcessingController) CropAndUpload(c *gin.Context) {
	// Get image from form data
	file, err := c.FormFile("image")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "No image file provided"})
		return
	}

	// Get crop parameters
	var cropParams struct {
		X      int `json:"x"`
		Y      int `json:"y"`
		Width  int `json:"width"`
		Height int `json:"height"`
	}
	
	cropData := c.PostForm("crop_data")
	if cropData != "" {
		if err := json.Unmarshal([]byte(cropData), &cropParams); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid crop parameters"})
			return
		}
	}

	quality := 85
	if q := c.PostForm("quality"); q != "" {
		fmt.Sscanf(q, "%d", &quality)
	}

	maxWidth := 1500
	if mw := c.PostForm("max_width"); mw != "" {
		fmt.Sscanf(mw, "%d", &maxWidth)
	}

	// Open uploaded file
	src, err := file.Open()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to open uploaded file"})
		return
	}
	defer src.Close()

	// Decode image
	img, format, err := image.Decode(src)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image format"})
		return
	}

	// Apply crop if parameters provided
	processedImg := img
	if cropParams.Width > 0 && cropParams.Height > 0 {
		processedImg = imaging.Crop(processedImg, image.Rect(
			cropParams.X,
			cropParams.Y,
			cropParams.X+cropParams.Width,
			cropParams.Y+cropParams.Height,
		))
	}

	// Resize if larger than max width
	bounds := processedImg.Bounds()
	if bounds.Dx() > maxWidth {
		processedImg = imaging.Resize(processedImg, maxWidth, 0, imaging.Lanczos)
	}

	// Save to uploads
	outputPath, err := ctrl.saveProcessedImage(processedImg, format, quality)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save processed image"})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"url": outputPath,
	})
}

// QuickEdit handles common quick edits in one call
func (ctrl *ImageProcessingController) QuickEdit(c *gin.Context) {
	var req struct {
		ImageURL   string  `json:"image_url"`
		Width      int     `json:"width"`
		Rotation   int     `json:"rotation"`
		FlipH      bool    `json:"flip_h"`
		FlipV      bool    `json:"flip_v"`
		Brightness float64 `json:"brightness"`
		Contrast   float64 `json:"contrast"`
		Saturation float64 `json:"saturation"`
		Grayscale  bool    `json:"grayscale"`
		Quality    int     `json:"quality"`
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
		return
	}

	if req.Quality <= 0 || req.Quality > 100 {
		req.Quality = 85
	}

	// Load image
	img, format, err := ctrl.loadImage(req.ImageURL)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to load image: " + err.Error()})
		return
	}

	processedImg := img

	// Resize if width specified
	if req.Width > 0 {
		processedImg = imaging.Resize(processedImg, req.Width, 0, imaging.Lanczos)
	}

	// Rotate
	if req.Rotation != 0 {
		switch req.Rotation % 360 {
		case 90, -270:
			processedImg = imaging.Rotate90(processedImg)
		case 180, -180:
			processedImg = imaging.Rotate180(processedImg)
		case 270, -90:
			processedImg = imaging.Rotate270(processedImg)
		}
	}

	// Flip
	if req.FlipH {
		processedImg = imaging.FlipH(processedImg)
	}
	if req.FlipV {
		processedImg = imaging.FlipV(processedImg)
	}

	// Brightness/Contrast/Saturation
	if req.Brightness != 0 {
		processedImg = imaging.AdjustBrightness(processedImg, req.Brightness)
	}
	if req.Contrast != 0 {
		processedImg = imaging.AdjustContrast(processedImg, req.Contrast)
	}
	if req.Saturation != 0 {
		processedImg = imaging.AdjustSaturation(processedImg, req.Saturation)
	}

	// Grayscale
	if req.Grayscale {
		processedImg = imaging.Grayscale(processedImg)
	}

	// Save
	outputPath, err := ctrl.saveProcessedImage(processedImg, format, req.Quality)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save image"})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"url": outputPath,
	})
}
