package handlers

import (
	"net/http"
	"os"
	"strconv"
	"strings"

	"gauth-central/config"
	"gauth-central/internal/services"
	"gauth-central/pkg/utils"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

type PostHandler struct {
	postService *services.PostService
}

func NewPostHandler(postService *services.PostService) *PostHandler {
	return &PostHandler{postService: postService}
}

// GetAllPosts godoc
// @Summary Get all active posts with pagination
// @Description Retrieve a list of active posts with pagination. Use front=true for front posts only
// @Tags posts
// @Produce json
// @Param front query bool false "Front posts only"
// @Param page query int false "Page number" default(1)
// @Param limit query int false "Items per page" default(10)
// @Success 200 {object} map[string]interface{}
// @Failure 500 {object} map[string]string
// @Router /posts [get]
// GetAllPosts returns active posts with pagination. Optional query: front=true for front posts only.
func (h *PostHandler) GetAllPosts(c *gin.Context) {
	onlyFront := false
	if raw := strings.TrimSpace(c.Query("front")); raw != "" {
		parsed, err := strconv.ParseBool(raw)
		if err == nil {
			onlyFront = parsed
		}
	}

	page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))

	if page < 1 {
		page = 1
	}
	if limit < 1 || limit > 100 {
		limit = 10
	}

	items, total, err := h.postService.GetAllPosts(true, onlyFront, page, limit)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"data":  items,
		"total": total,
		"page":  page,
		"limit": limit,
	})
}

// GetPostBySlug godoc
// @Summary Get post by slug
// @Description Retrieve an active post by slug
// @Tags posts
// @Produce json
// @Param slug path string true "Post Slug"
// @Success 200 {object} models.Post
// @Failure 404 {object} map[string]string
// @Router /posts/slug/{slug} [get]
// GetPostBySlug returns a post by slug.
func (h *PostHandler) GetPostBySlug(c *gin.Context) {
	slug := c.Param("slug")
	item, err := h.postService.GetPostBySlug(slug, true)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, item)
}

// AdminGetAllPosts godoc
// @Summary Get all posts (Admin)
// @Description Retrieve a list of all posts including inactive ones with pagination
// @Tags admin
// @Security ApiKeyAuth
// @Produce json
// @Param page query int false "Page number" default(1)
// @Param limit query int false "Items per page" default(10)
// @Success 200 {object} map[string]interface{}
// @Failure 500 {object} map[string]string
// @Router /admin/posts [get]
// AdminGetAllPosts returns all posts with pagination.
func (h *PostHandler) AdminGetAllPosts(c *gin.Context) {
	page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))

	if page < 1 {
		page = 1
	}
	if limit < 1 || limit > 100 {
		limit = 10
	}

	items, total, err := h.postService.GetAllPosts(false, false, page, limit)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"data":  items,
		"total": total,
		"page":  page,
		"limit": limit,
	})
}

// AdminGetPostByID godoc
// @Summary Get post by ID (Admin)
// @Description Retrieve a post by ID
// @Tags admin
// @Security ApiKeyAuth
// @Produce json
// @Param id path string true "Post ID"
// @Success 200 {object} models.Post
// @Failure 404 {object} map[string]string
// @Router /admin/posts/{id} [get]
// AdminGetPostByID returns a post by ID.
func (h *PostHandler) AdminGetPostByID(c *gin.Context) {
	id := c.Param("id")
	item, err := h.postService.GetPostByID(id)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, item)
}

// CreatePost godoc
// @Summary Create a post (Admin)
// @Description Create a new post
// @Tags admin
// @Security ApiKeyAuth
// @Accept multipart/form-data
// @Produce json
// @Param title formData string true "Title"
// @Param content formData string false "Content"
// @Param keywords formData string true "Keywords"
// @Param video formData string false "Video"
// @Param category_ids formData []string false "Category IDs"
// @Param tag_ids formData []string false "Tag IDs"
// @Param parent_id formData string false "Parent ID"
// @Param image formData file false "Image"
// @Param is_active formData bool false "Is active"
// @Param is_front formData bool false "Is front"
// @Success 201 {object} models.Post
// @Failure 400 {object} map[string]string
// @Router /admin/posts [post]
// CreatePost creates a new post.
func (h *PostHandler) CreatePost(c *gin.Context) {
	title := strings.TrimSpace(c.PostForm("title"))
	content := strings.TrimSpace(c.PostForm("content"))
	keywords := strings.TrimSpace(c.PostForm("keywords"))
	video := strings.TrimSpace(c.PostForm("video"))
	if title == "" || keywords == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "title and keywords are required"})
		return
	}
	if video == "" {
		video = "none"
	}

	parentID, err := parseUUIDPtr(c.PostForm("parent_id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid parent_id"})
		return
	}

	isActive := true
	isActivePtr, err := parseOptionalBool(c, "is_active")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "is_active must be true or false"})
		return
	}
	if isActivePtr != nil {
		isActive = *isActivePtr
	}

	isFront := true
	isFrontPtr, err := parseOptionalBool(c, "is_front")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "is_front must be true or false"})
		return
	}
	if isFrontPtr != nil {
		isFront = *isFrontPtr
	}

	categoryIDs := parseIDList(c, "category_ids")
	tagIDs := parseIDList(c, "tag_ids")

	imageURL := ""
	file, fileErr := c.FormFile("image")
	if fileErr == nil {
		if file.Size > 5*1024*1024 {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Image size exceeds 5MB limit"})
			return
		}

		saved, saveErr := utils.SaveOptimizedImage(file, "./uploads/posts", "post", &utils.ImageOptions{
			Width:   config.AppConfig.PostImageWidth,
			Height:  config.AppConfig.PostImageHeight,
			Quality: float32(config.AppConfig.PostImageQuality),
			Format:  config.AppConfig.PostImageFormat,
			Mode:    config.AppConfig.PostImageMode,
		})
		if saveErr != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save image: " + saveErr.Error()})
			return
		}
		imageURL = saved
	}

	item, err := h.postService.CreatePost(
		title,
		content,
		keywords,
		imageURL,
		video,
		categoryIDs,
		tagIDs,
		parentID,
		isActive,
		isFront,
	)
	if err != nil {
		status := http.StatusInternalServerError
		switch err.Error() {
		case "slug cannot be empty", "slug already exists", "one or more categories not found", "one or more tags not found":
			status = http.StatusBadRequest
		}
		c.JSON(status, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusCreated, item)
}

// UpdatePost godoc
// @Summary Update a post (Admin)
// @Description Update an existing post
// @Tags admin
// @Security ApiKeyAuth
// @Accept multipart/form-data
// @Produce json
// @Param id path string true "Post ID"
// @Param title formData string false "Title"
// @Param content formData string false "Content"
// @Param keywords formData string false "Keywords"
// @Param video formData string false "Video"
// @Param category_ids formData []string false "Category IDs"
// @Param tag_ids formData []string false "Tag IDs"
// @Param parent_id formData string false "Parent ID"
// @Param image formData file false "Image"
// @Param slug formData string false "Slug"
// @Param is_active formData bool false "Is active"
// @Param is_front formData bool false "Is front"
// @Success 200 {object} models.Post
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /admin/posts/{id} [put]
// UpdatePost updates a post.
func (h *PostHandler) UpdatePost(c *gin.Context) {
	id := c.Param("id")

	title, hasTitle := getOptionalFormValue(c, "title")
	content, hasContent := getOptionalFormValue(c, "content")
	keywords, hasKeywords := getOptionalFormValue(c, "keywords")
	video, hasVideo := getOptionalFormValue(c, "video")
	slug, hasSlug := getOptionalFormValue(c, "slug")

	var titlePtr *string
	var contentPtr *string
	var keywordsPtr *string
	var videoPtr *string
	var slugPtr *string

	if hasTitle {
		titlePtr = &title
	}
	if hasContent {
		contentPtr = &content
	}
	if hasKeywords {
		keywordsPtr = &keywords
	}
	if hasVideo {
		videoPtr = &video
	}
	if hasSlug {
		slugPtr = &slug
	}

	parentIDValue, hasParent := getOptionalFormValue(c, "parent_id")
	parentIDSet := false
	var parentIDPtr *uuid.UUID
	if hasParent {
		parentIDSet = true
		if parentIDValue == "" {
			parentIDPtr = nil
		} else {
			parsed, err := parseUUIDPtr(parentIDValue)
			if err != nil {
				c.JSON(http.StatusBadRequest, gin.H{"error": "invalid parent_id"})
				return
			}
			parentIDPtr = parsed
		}
	}

	isActivePtr, err := parseOptionalBool(c, "is_active")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "is_active must be true or false"})
		return
	}

	isFrontPtr, err := parseOptionalBool(c, "is_front")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "is_front must be true or false"})
		return
	}

	var categoryIDsPtr *[]string
	if hasIDList(c, "category_ids") {
		ids := parseIDList(c, "category_ids")
		categoryIDsPtr = &ids
	}

	var tagIDsPtr *[]string
	if hasIDList(c, "tag_ids") {
		ids := parseIDList(c, "tag_ids")
		tagIDsPtr = &ids
	}

	var imagePtr *string
	file, fileErr := c.FormFile("image")
	if fileErr == nil {
		if file.Size > 5*1024*1024 {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Image size exceeds 5MB limit"})
			return
		}

		item, fetchErr := h.postService.GetPostByID(id)
		if fetchErr != nil {
			c.JSON(http.StatusNotFound, gin.H{"error": fetchErr.Error()})
			return
		}

		if item.Image != "" && strings.HasPrefix(item.Image, "/uploads/") {
			_ = os.Remove("." + item.Image)
		}

		saved, saveErr := utils.SaveOptimizedImage(file, "./uploads/posts", id, &utils.ImageOptions{
			Width:   config.AppConfig.PostImageWidth,
			Height:  config.AppConfig.PostImageHeight,
			Quality: float32(config.AppConfig.PostImageQuality),
			Format:  config.AppConfig.PostImageFormat,
			Mode:    config.AppConfig.PostImageMode,
		})
		if saveErr != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save image: " + saveErr.Error()})
			return
		}
		imagePtr = &saved
	}

	item, err := h.postService.UpdatePost(
		id,
		titlePtr,
		contentPtr,
		keywordsPtr,
		imagePtr,
		videoPtr,
		categoryIDsPtr,
		tagIDsPtr,
		parentIDPtr,
		parentIDSet,
		slugPtr,
		isActivePtr,
		isFrontPtr,
	)
	if err != nil {
		status := http.StatusInternalServerError
		switch err.Error() {
		case "post not found":
			status = http.StatusNotFound
		case "slug cannot be empty", "slug already exists", "one or more categories not found", "one or more tags not found":
			status = http.StatusBadRequest
		}
		c.JSON(status, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, item)
}

// DeletePost godoc
// @Summary Delete a post (Admin)
// @Description Delete a post by ID
// @Tags admin
// @Security ApiKeyAuth
// @Param id path string true "Post ID"
// @Success 200 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /admin/posts/{id} [delete]
// DeletePost deletes a post by ID.
func (h *PostHandler) DeletePost(c *gin.Context) {
	id := c.Param("id")
	if err := h.postService.DeletePost(id); err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"message": "Post deleted successfully"})
}

func parseIDList(c *gin.Context, key string) []string {
	ids := c.PostFormArray(key)
	if len(ids) == 0 {
		raw := strings.TrimSpace(c.PostForm(key))
		if raw == "" {
			return ids
		}
		parts := strings.Split(raw, ",")
		for _, part := range parts {
			trimmed := strings.TrimSpace(part)
			if trimmed != "" {
				ids = append(ids, trimmed)
			}
		}
	}
	return ids
}

func hasIDList(c *gin.Context, key string) bool {
	if len(c.PostFormArray(key)) > 0 {
		return true
	}
	if raw := strings.TrimSpace(c.PostForm(key)); raw != "" {
		return true
	}
	return false
}
