package images

import (
	"fmt"
	"io"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/gofiber/fiber/v3"
	"github.com/google/uuid"
	"github.com/h2non/bimg"

	"goimgApi/accounts"
	"goimgApi/configs"
)

// Upload godoc
// @Summary Upload an image
// @Description Uploads an image and registers it to the user.
// @Tags Images
// @Accept multipart/form-data
// @Produce json
// @Security BearerAuth
// @Param image formData file true "Image file"
// @Param w formData int false "Width"
// @Param h formData int false "Height"
// @Param q formData int false "Quality (1-100)"
// @Param f formData string false "Format (webp, avif, png, jpg)"
// @Param mode formData string false "Mode (e.g. cover)"
// @Success 201 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Failure 401 {object} map[string]interface{}
// @Router /images [post]
// Upload saves an image file to local storage and creates a DB record
func Upload(c fiber.Ctx) error {
	userIDVal := c.Locals("user_id")
	if userIDVal == nil {
		return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"})
	}
	userID := userIDVal.(uint)

	file, err := c.FormFile("image")
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Failed to upload image file"})
	}

	// Resim dosyasını belleğe okuyoruz
	fileHeader, err := file.Open()
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to open image file"})
	}
	defer func() {
		_ = fileHeader.Close()
	}()

	buffer, err := io.ReadAll(fileHeader)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to read image file"})
	}

	// Parametreleri Form verisinden alıyoruz
	opts := ProcessOptions{}
	opts.Width, _ = strconv.Atoi(c.FormValue("w"))
	opts.Height, _ = strconv.Atoi(c.FormValue("h"))
	opts.Quality, _ = strconv.Atoi(c.FormValue("q"))
	opts.Format = c.FormValue("f")
	if c.FormValue("mode") == "cover" {
		opts.Cover = true
	}

	// Eğer herhangi bir düzenleme parametresi geldiyse, önce işliyoruz
	if opts.Width > 0 || opts.Height > 0 || opts.Format != "" || opts.Quality > 0 {
		processedBuffer, err := ProcessImage(buffer, opts)
		if err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Image processing failed"})
		}
		buffer = processedBuffer
	}

	// Generate a unique filename using UUID
	parts := strings.Split(file.Filename, ".")
	ext := ""
	if len(parts) > 1 {
		ext = "." + parts[len(parts)-1]
	}
	if opts.Format != "" {
		ext = "." + strings.ToLower(opts.Format)
	}

	newFilename := fmt.Sprintf("%s%s", uuid.New().String(), ext)
	uploadPath := imageDiskPath(newFilename)
	publicPath := imagePublicPath(newFilename)

	if err := os.MkdirAll("uploads", 0755); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to prepare upload directory"})
	}

	if err := os.WriteFile(uploadPath, buffer, 0644); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to save file"})
	}

	sizeInfo, _ := bimg.NewImage(buffer).Size()
	imgFormat := bimg.NewImage(buffer).Type()

	quality := opts.Quality
	if quality == 0 {
		quality = 85 // default bimg quality
	}
	mode := c.FormValue("mode")
	if mode == "" {
		mode = "original"
	}

	sizeInKB := int64(len(buffer)) / 1024
	if sizeInKB == 0 && len(buffer) > 0 {
		sizeInKB = 1 // 1 KB'dan küçükse minimum 1 KB göster
	}

	img := Image{
		UserID:     userID,
		Filename:   newFilename,
		PublicPath: publicPath,
		MimeType:   file.Header.Get("Content-Type"),
		Size:       sizeInKB,
		Width:      sizeInfo.Width,
		Height:     sizeInfo.Height,
		Quality:    quality,
		Format:     imgFormat,
		Mode:       mode,
	}

	if err := configs.DB.Create(&img).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to save image record"})
	}

	return c.Status(fiber.StatusCreated).JSON(fiber.Map{
		"message":     "Image uploaded successfully",
		"image_id":    img.ID,
		"filename":    img.Filename,
		"public_path": img.PublicPath,
		"image_url":   buildImageURL(c, img.PublicPath),
	})
}

// ListImages godoc
// @Summary List images
// @Description Returns a paginated list of images belonging to the authenticated user.
// @Tags Images
// @Produce json
// @Security BearerAuth
// @Param page  query int false "Page number (default 1)"
// @Param limit query int false "Items per page (default 20, max 100)"
// @Success 200 {object} map[string]interface{}
// @Failure 401 {object} map[string]interface{}
// @Router /images [get]
func ListImages(c fiber.Ctx) error {
	userIDVal := c.Locals("user_id")
	if userIDVal == nil {
		return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"})
	}
	userID := userIDVal.(uint)

	page, limit := parsePagination(c)
	offset := (page - 1) * limit

	var total int64
	if err := configs.DB.Model(&Image{}).Where("user_id = ?", userID).Count(&total).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to count images"})
	}

	var imgs []Image
	if err := configs.DB.
		Where("user_id = ?", userID).
		Order("created_at DESC").
		Limit(limit).
		Offset(offset).
		Find(&imgs).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to fetch images"})
	}

	return c.JSON(fiber.Map{
		"data":  enrichImages(c, imgs),
		"total": total,
		"page":  page,
		"limit": limit,
	})
}

// GetImage godoc
// @Summary Get image by ID
// @Description Returns a single image record owned by the authenticated user.
// @Tags Images
// @Produce json
// @Security BearerAuth
// @Param id path int true "Image ID"
// @Success 200 {object} map[string]interface{}
// @Failure 401 {object} map[string]interface{}
// @Failure 404 {object} map[string]interface{}
// @Router /images/{id} [get]
func GetImage(c fiber.Ctx) error {
	userIDVal := c.Locals("user_id")
	if userIDVal == nil {
		return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"})
	}
	userID := userIDVal.(uint)

	var img Image
	if err := configs.DB.Where("id = ? AND user_id = ?", c.Params("id"), userID).First(&img).Error; err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Image not found"})
	}

	return c.JSON(enrichImage(c, img))
}

// AdminListImages godoc
// @Summary List all images (admin)
// @Description Returns a paginated list of all images across all users.
// @Tags Admin
// @Produce json
// @Security BearerAuth
// @Param page      query int false "Page number (default 1)"
// @Param limit     query int false "Items per page (default 20, max 100)"
// @Param user_id   query int false "Filter by user ID"
// @Success 200 {object} map[string]interface{}
// @Failure 401 {object} map[string]interface{}
// @Failure 403 {object} map[string]interface{}
// @Router /admin/images [get]
func AdminListImages(c fiber.Ctx) error {
	page, limit := parsePagination(c)
	offset := (page - 1) * limit

	query := configs.DB.Model(&Image{})
	if uid := c.Query("user_id"); uid != "" {
		query = query.Where("user_id = ?", uid)
	}

	var total int64
	if err := query.Count(&total).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to count images"})
	}

	var imgs []Image
	if err := query.
		Order("created_at DESC").
		Limit(limit).
		Offset(offset).
		Find(&imgs).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to fetch images"})
	}

	return c.JSON(fiber.Map{
		"data":  enrichImages(c, imgs),
		"total": total,
		"page":  page,
		"limit": limit,
	})
}

// ---------- yardımcılar ----------

func parsePagination(c fiber.Ctx) (page, limit int) {
	page, _ = strconv.Atoi(c.Query("page", "1"))
	limit, _ = strconv.Atoi(c.Query("limit", "20"))
	if page < 1 {
		page = 1
	}
	if limit < 1 || limit > 100 {
		limit = 20
	}
	return
}

type imageResponse struct {
	ID         uint      `json:"id"`
	UserID     uint      `json:"user_id"`
	Filename   string    `json:"filename"`
	PublicPath string    `json:"public_path"`
	ImageURL   string    `json:"image_url"`
	MimeType   string    `json:"mime_type"`
	Size       int64     `json:"size_kb"`
	Width      int       `json:"width"`
	Height     int       `json:"height"`
	Quality    int       `json:"quality"`
	Format     string    `json:"format"`
	Mode       string    `json:"mode"`
	CreatedAt  time.Time `json:"created_at"`
}

func enrichImage(c fiber.Ctx, img Image) imageResponse {
	return imageResponse{
		ID:         img.ID,
		UserID:     img.UserID,
		Filename:   img.Filename,
		PublicPath: img.PublicPath,
		ImageURL:   buildImageURL(c, img.PublicPath),
		MimeType:   img.MimeType,
		Size:       img.Size,
		Width:      img.Width,
		Height:     img.Height,
		Quality:    img.Quality,
		Format:     img.Format,
		Mode:       img.Mode,
		CreatedAt:  img.CreatedAt,
	}
}

func enrichImages(c fiber.Ctx, imgs []Image) []imageResponse {
	result := make([]imageResponse, 0, len(imgs))
	for _, img := range imgs {
		result = append(result, enrichImage(c, img))
	}
	return result
}

// Process godoc
// @Summary Process Image
// @Description Processes an image (resize, crop, cover, format) using the generated token.
// @Tags Images
// @Produce image/jpeg,image/png,image/webp,image/avif
// @Param id path int true "Image ID"
// @Param token query string true "Global API Token"
// @Param w query int false "Width"
// @Param h query int false "Height"
// @Param q query int false "Quality (1-100)"
// @Param f query string false "Format (webp, avif, png, jpg)"
// @Param mode query string false "Mode (e.g. cover)"
// @Success 200 {file} file
// @Failure 401 {object} map[string]interface{}
// @Failure 404 {object} map[string]interface{}
// @Failure 500 {object} map[string]interface{}
// @Router /images/{id}/process [get]
// Process handles image manipulation via bimg
func Process(c fiber.Ctx) error {
	token := c.Query("token")
	if token == "" {
		return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "API Token required"})
	}

	imageIDStr := c.Params("id")

	// Validate the API Token belongs to a registered User
	var tokenUser accounts.User
	if err := configs.DB.Where("api_token = ?", token).First(&tokenUser).Error; err != nil {
		return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid API Token"})
	}

	// Check Expiration
	if tokenUser.ApiTokenExpiresAt != nil && tokenUser.ApiTokenExpiresAt.Before(time.Now()) {
		return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "API Token is expired"})
	}

	var img Image
	if err := configs.DB.Where("id = ?", imageIDStr).First(&img).Error; err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Image not found"})
	}

	// Read image file from disk
	filePath := imageDiskPath(img.Filename)
	buffer, err := os.ReadFile(filePath)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to read image file from disk"})
	}

	// Parse query params
	opts := ProcessOptions{}
	opts.Width, _ = strconv.Atoi(c.Query("w"))
	opts.Height, _ = strconv.Atoi(c.Query("h"))
	opts.Quality, _ = strconv.Atoi(c.Query("q"))
	opts.Format = c.Query("f")

	if c.Query("mode") == "cover" {
		opts.Cover = true
	}

	processedBuffer, err := ProcessImage(buffer, opts)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Image processing failed"})
	}

	// Set content type and return
	contentType := "image/jpeg"
	switch strings.ToLower(opts.Format) {
	case "webp":
		contentType = "image/webp"
	case "png":
		contentType = "image/png"
	case "avif":
		contentType = "image/avif"
	}

	c.Set("Content-Type", contentType)
	return c.Send(processedBuffer)
}

func imagePublicPath(filename string) string {
	return "/uploads/" + strings.TrimLeft(filename, "/")
}

func imageDiskPath(filename string) string {
	return "uploads/" + strings.TrimLeft(filename, "/")
}

func buildImageURL(c fiber.Ctx, publicPath string) string {
	host := strings.TrimSpace(c.Get("X-Forwarded-Host"))
	if host == "" {
		host = strings.TrimSpace(c.Get("Host"))
	}
	if host == "" {
		return publicPath
	}

	scheme := strings.TrimSpace(c.Get("X-Forwarded-Proto"))
	if scheme == "" {
		scheme = c.Protocol()
	}

	return (&url.URL{Scheme: scheme, Host: host, Path: publicPath}).String()
}
