package controllers

// NOTE: minor edit test to verify file is writable

import (
	configs "ares/config"
	dbConfig "ares/database/config"
	"ares/database/models"
	"ares/middlewares"
	utils "ares/pkg/utis"
	"ares/services"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"net/http"
	"strconv"
	"strings"
	"time"

	"gorm.io/gorm"

	"github.com/gofiber/fiber/v3"
	"go.uber.org/zap"
	"golang.org/x/crypto/bcrypt"
)

// parseImagesField accepts the stored Images field which may be either a JSON
// array string like '["/uploads/..."]' or a plain string "/uploads/..." and
// returns a slice of image paths.
func parseImagesField(s string) []string {
	var imgs []string
	s = strings.TrimSpace(s)
	if s == "" {
		return imgs
	}
	// If it looks like a JSON array attempt to unmarshal
	if strings.HasPrefix(s, "[") {
		if err := json.Unmarshal([]byte(s), &imgs); err == nil {
			return imgs
		}
	}
	// Otherwise treat as a single path string (strip surrounding quotes if present)
	s = strings.Trim(s, "\"")
	if s != "" {
		imgs = append(imgs, s)
	}
	return imgs
}

// AdminLogin renders the login page
func AdminLogin(c fiber.Ctx) error {
	return c.Render("admin/login", fiber.Map{})
}

// AdminLoginPost handles the login form submission
func AdminLoginPost(c fiber.Ctx) error {
	email := c.FormValue("email")
	password := c.FormValue("password")
	turnstileToken := c.FormValue("cf-turnstile-response")

	// 1. Verify Turnstile Token
	if turnstileToken == "" {
		// return c.Status(fiber.StatusBadRequest).SendString("<div class='alert alert-danger'>Turnstile doğrulaması başarısız.</div>")
	}

	// 2. Verify Credentials against Database (include profile)
	var user models.User
	if err := dbConfig.DB.Preload("Profile").Where("email = ?", email).First(&user).Error; err != nil {
		return c.Status(fiber.StatusOK).SendString("<div class='alert alert-danger'>Hatalı e-posta veya şifre.</div>")
	}

	// Check password
	if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
		return c.Status(fiber.StatusOK).SendString("<div class='alert alert-danger'>Hatalı e-posta veya şifre.</div>")
	}

	// Check if admin
	if user.IsAdmin == nil || !*user.IsAdmin {
		return c.Status(fiber.StatusOK).SendString("<div class='alert alert-danger'>Bu alana erişim yetkiniz yok.</div>")
	}

	// Login success - generate signed JWT cookie
	jwtService := services.NewJWTService()
	first := ""
	last := ""
	if len(user.Profile) > 0 {
		first = user.Profile[0].FirstName
		last = user.Profile[0].LastName
	}
	accessToken, _, err := jwtService.GenerateTokenPair(uint(user.ID), user.Email, user.IsAdmin != nil && *user.IsAdmin, first, last)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("<div class='alert alert-danger'>Oturum oluşturulamadı.</div>")
	}

	cookie := new(fiber.Cookie)
	cookie.Name = "admin_session"
	cookie.Value = accessToken
	cookie.Expires = time.Now().Add(time.Duration(configs.AppConfig.AccessTokenExpireMinutes) * time.Minute)
	cookie.Path = "/"
	cookie.HTTPOnly = true
	cookie.Secure = true
	cookie.SameSite = "Strict"
	c.Cookie(cookie)

	// Check if request is from HTMX
	if c.Get("HX-Request") == "true" {
		c.Set("HX-Redirect", "/admin")
		return c.SendStatus(fiber.StatusOK)
	}

	// Standard redirect for non-HTMX requests (fallback)
	return c.Redirect().To("/admin")
}

// AdminLogout clears the session
func AdminLogout(c fiber.Ctx) error {
	c.ClearCookie("admin_session")
	return c.Redirect().To("/login")
}

// AdminDashboard renders the full layout with dashboard content
func AdminDashboard(c fiber.Ctx) error {
	return c.Render("admin/partials/dashboard", fiber.Map{
		"Title": "Dashboard",
	}, "admin/layout")
}

// AdminContentDashboard renders the dashboard partial (for HTMX) or full page
func AdminContentDashboard(c fiber.Ctx) error {
	if c.Get("HX-Request") == "true" {
		return c.Render("admin/partials/dashboard", fiber.Map{})
	}
	return c.Render("admin/partials/dashboard", fiber.Map{
		"Title": "Dashboard",
	}, "admin/layout")
}

// AdminMe returns basic info about the currently authenticated admin (name, email, avatar)
func AdminMe(c fiber.Ctx) error {
	claims, ok := middlewares.GetAuthClaims(c)
	if !ok {
		return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "unauthorized"})
	}

	name := strings.TrimSpace(claims.FirstName + " " + claims.LastName)
	if name == "" {
		name = claims.Email
	}

	avatar := ""
	var user models.User
	if err := dbConfig.DB.Preload("Profile").First(&user, claims.UserID).Error; err == nil {
		if len(user.Profile) > 0 {
			avatar = user.Profile[0].AvatarURL
		}
	}

	return c.JSON(fiber.Map{"name": name, "email": claims.Email, "avatar": avatar})
}

// AdminContentUsers renders the users partial with pagination and search
func AdminContentUsers(c fiber.Ctx) error {
	page, _ := strconv.Atoi(c.Query("page", "1"))
	limit := 10
	offset := (page - 1) * limit
	search := c.Query("search", "")
	showDeleted := c.Query("deleted") == "true"

	var users []models.User
	var total int64

	query := dbConfig.DB.Model(&models.User{})

	if showDeleted {
		query = query.Unscoped().Where("deleted_at IS NOT NULL")
	}

	if search != "" {
		query = query.Where("user_name LIKE ? OR email LIKE ?", "%"+search+"%", "%"+search+"%")
	}

	query.Count(&total)
	query.Limit(limit).Offset(offset).Order("created_at desc").Find(&users)

	totalPages := int(math.Ceil(float64(total) / float64(limit)))

	// Check if request is from HTMX
	if c.Get("HX-Request") == "true" {
		return c.Render("admin/partials/users", fiber.Map{
			"Users":       users,
			"Page":        page,
			"TotalPages":  totalPages,
			"NextPage":    page + 1,
			"PrevPage":    page - 1,
			"Search":      search,
			"ShowDeleted": showDeleted,
		})
	}

	// If not HTMX (e.g. page refresh), render with layout
	return c.Render("admin/partials/users", fiber.Map{
		"Users":       users,
		"Page":        page,
		"TotalPages":  totalPages,
		"NextPage":    page + 1,
		"PrevPage":    page - 1,
		"Search":      search,
		"ShowDeleted": showDeleted,
	}, "admin/layout")
}

// AdminUserNew renders the create user full page form
func AdminUserNew(c fiber.Ctx) error {
	return c.Render("admin/pages/user_form", fiber.Map{
		"IsEdit": false,
	}, "admin/layout")
}

// AdminUserCreate handles user creation
func AdminUserCreate(c fiber.Ctx) error {
	username := c.FormValue("username")
	email := c.FormValue("email")
	password := c.FormValue("password")
	isAdmin := c.FormValue("is_admin") == "on"
	emailVerified := c.FormValue("email_verified") == "on"

	// Basic validation
	if username == "" || email == "" || password == "" {
		return c.Status(fiber.StatusBadRequest).SendString("<div class='alert alert-danger'>Lütfen tüm alanları doldurun.</div>")
	}

	// Hash password
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("<div class='alert alert-danger'>Şifre oluşturulurken hata.</div>")
	}

	user := models.User{
		UserName:      username,
		Email:         email,
		Password:      string(hashedPassword),
		IsAdmin:       &isAdmin,
		EmailVerified: &emailVerified,
	}

	if err := dbConfig.DB.Create(&user).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("<div class='alert alert-danger'>Kullanıcı oluşturulurken hata: " + err.Error() + "</div>")
	}

	// If profile fields or avatar provided, process and create profile
	firstName := c.FormValue("first_name")
	lastName := c.FormValue("last_name")
	avatarPath := ""
	if p, err := services.ProcessAndSaveImage(c, "avatar", services.ImageOptions{
		Width:   150,
		Height:  150,
		Quality: 85,
		Format:  "avif",
		Folder:  "avatars",
	}); err == nil {
		avatarPath = p
	}

	if firstName != "" || lastName != "" || avatarPath != "" {
		profile := models.Profile{UserID: uint64(user.ID)}
		if firstName != "" {
			profile.FirstName = firstName
		}
		if lastName != "" {
			profile.LastName = lastName
		}
		if avatarPath != "" {
			profile.AvatarURL = avatarPath
		}
		dbConfig.DB.Create(&profile)
	}

	return c.Redirect().To("/admin/content/users?success=Kullanıcı+başarıyla+oluşturuldu")
}

// AdminUserEdit renders the edit user full page form
func AdminUserEdit(c fiber.Ctx) error {
	id := c.Params("id")
	var user models.User
	if err := dbConfig.DB.Preload("Profile").First(&user, id).Error; err != nil {
		return c.Status(fiber.StatusNotFound).SendString("Kullanıcı bulunamadı")
	}

	return c.Render("admin/pages/user_form", fiber.Map{
		"IsEdit": true,
		"User":   user,
	}, "admin/layout")
}

// AdminUserUpdate handles user update
func AdminUserUpdate(c fiber.Ctx) error {
	id := c.Params("id")
	var user models.User
	if err := dbConfig.DB.Preload("Profile").First(&user, id).Error; err != nil {
		return c.Status(fiber.StatusNotFound).SendString("Kullanıcı bulunamadı")
	}

	user.UserName = c.FormValue("username")
	user.Email = c.FormValue("email")

	isAdmin := c.FormValue("is_admin") == "on"
	user.IsAdmin = &isAdmin

	emailVerified := c.FormValue("email_verified") == "on"
	user.EmailVerified = &emailVerified

	// Update password only if provided
	newPassword := c.FormValue("password")
	if newPassword != "" {
		hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
		if err != nil {
			return c.Status(fiber.StatusInternalServerError).SendString("<div class='alert alert-danger'>Şifre güncellenirken hata.</div>")
		}
		user.Password = string(hashedPassword)
	}

	if err := dbConfig.DB.Save(&user).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("<div class='alert alert-danger'>Güncelleme hatası: " + err.Error() + "</div>")
	}

	// Handle profile fields and avatar upload
	firstName := c.FormValue("first_name")
	lastName := c.FormValue("last_name")

	// Avatar file handling via image service (150x150, AVIF)
	avatarPath := ""
	if p, err := services.ProcessAndSaveImage(c, "avatar", services.ImageOptions{
		Width:   150,
		Height:  150,
		Quality: 85,
		Format:  "avif",
		Folder:  "avatars",
	}); err == nil {
		avatarPath = p
	}

	// Update or create profile
	if len(user.Profile) > 0 {
		profile := user.Profile[0]
		if firstName != "" {
			profile.FirstName = firstName
		}
		if lastName != "" {
			profile.LastName = lastName
		}
		if avatarPath != "" {
			profile.AvatarURL = avatarPath
		}
		if err := dbConfig.DB.Model(&profile).Updates(profile).Error; err != nil {
			// continue but log error
		}
	} else {
		// create
		newProfile := models.Profile{UserID: uint64(user.ID)}
		if firstName != "" {
			newProfile.FirstName = firstName
		}
		if lastName != "" {
			newProfile.LastName = lastName
		}
		if avatarPath != "" {
			newProfile.AvatarURL = avatarPath
		}
		if err := dbConfig.DB.Create(&newProfile).Error; err != nil {
			// continue
		}
	}

	return c.Redirect().To("/admin/content/users?success=Kullanıcı+başarıyla+güncellendi")
}

// AdminUserDelete handles user soft delete
func AdminUserDelete(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Delete(&models.User{}, id).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("Silme hatası")
	}

	// Return updated list or trigger
	return c.Redirect().To("/admin/content/users?success=Kullanıcı+silindi")
}

// AdminUserRestore restores a soft-deleted user
func AdminUserRestore(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Unscoped().Model(&models.User{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("Geri yükleme hatası")
	}

	return c.Redirect().To("/admin/content/users?deleted=true&success=Kullanıcı+geri+yüklendi")
}

// AdminContentSettings renders the settings partial with General Settings and Hero list
func AdminContentSettings(c fiber.Ctx) error {
	// 1. Fetch General Settings
	var setting models.Setting
	if err := dbConfig.DB.First(&setting).Error; err != nil {
		// If not found, create default? Or just empty.
		// For now, empty is fine, UI should handle it.
	}

	// 2. Fetch Heroes
	var heroes []models.Hero
	showDeleted := c.Query("deleted") == "true"
	query := dbConfig.DB.Model(&models.Hero{})

	if showDeleted {
		query = query.Unscoped().Where("deleted_at IS NOT NULL")
	}

	query.Order("created_at desc").Find(&heroes)

	// 3. Fetch CORS lists and rate limits
	var corsWhitelist []models.CorsWhitelist
	var corsBlacklist []models.CorsBlacklist
	var rateLimits []models.RateLimitSetting

	// Respect showDeleted flag for soft-deleted items
	if showDeleted {
		dbConfig.DB.Unscoped().Where("deleted_at IS NOT NULL").Order("created_at desc").Find(&corsWhitelist)
		dbConfig.DB.Unscoped().Where("deleted_at IS NOT NULL").Order("created_at desc").Find(&corsBlacklist)
		dbConfig.DB.Unscoped().Where("deleted_at IS NOT NULL").Order("created_at desc").Find(&rateLimits)
	} else {
		dbConfig.DB.Order("created_at desc").Find(&corsWhitelist)
		dbConfig.DB.Order("created_at desc").Find(&corsBlacklist)
		dbConfig.DB.Order("created_at desc").Find(&rateLimits)
	}

	// Check if we're editing an existing entry (via query params)
	editWhitelistID := c.Query("edit_whitelist", "")
	var editWhitelist models.CorsWhitelist
	if editWhitelistID != "" {
		if err := dbConfig.DB.First(&editWhitelist, editWhitelistID).Error; err == nil {
			// found, will pass to template
		}
	}

	editBlacklistID := c.Query("edit_blacklist", "")
	var editBlacklist models.CorsBlacklist
	if editBlacklistID != "" {
		if err := dbConfig.DB.First(&editBlacklist, editBlacklistID).Error; err == nil {
		}
	}

	editRateLimitID := c.Query("edit_ratelimit", "")
	var editRateLimit models.RateLimitSetting
	if editRateLimitID != "" {
		if err := dbConfig.DB.First(&editRateLimit, editRateLimitID).Error; err == nil {
		}
	}

	data := fiber.Map{
		"Setting":       setting,
		"Heroes":        heroes,
		"ShowDeleted":   showDeleted,
		"CorsWhitelist": corsWhitelist,
		"CorsBlacklist": corsBlacklist,
		"RateLimits":    rateLimits,
		"EditWhitelist": editWhitelist,
		"EditBlacklist": editBlacklist,
		"EditRateLimit": editRateLimit,
	}

	if c.Get("HX-Request") == "true" {
		return c.Render("admin/partials/settings", data)
	}
	return c.Render("admin/partials/settings", data, "admin/layout")
}

// AdminSettingsPost handles the settings form submission
func AdminSettingsPost(c fiber.Ctx) error {
	configs.Logger.Info(
		"AdminSettingsPost called",
		zap.String("method", c.Method()),
		zap.String("path", c.Path()),
		zap.String("content_type", c.Get("Content-Type")),
	)

	if form, err := c.MultipartForm(); err == nil && form != nil {
		configs.Logger.Info(
			"AdminSettingsPost multipart received",
			zap.Int("w_logo_count", len(form.File["w_logo"])),
			zap.Int("b_logo_count", len(form.File["b_logo"])),
		)
	} else {
		configs.Logger.Warn("AdminSettingsPost multipart parse failed", zap.Error(err))
	}

	var setting models.Setting
	// Fetch existing or create new
	if err := dbConfig.DB.First(&setting).Error; err != nil {
		// Create new if doesn't exist
		setting = models.Setting{}
	}

	// Eski logo yollarını sakla; yeni dosya yüklenmezse bunları koruyacağız
	oldWLogo := setting.WLogo
	oldBLogo := setting.BLogo

	// Parse form
	if err := c.Bind().Body(&setting); err != nil {
		return c.Status(fiber.StatusBadRequest).SendString("<div class='alert alert-danger'>Form verileri okunamadı.</div>")
	}

	// Handle checkboxes (boolean) manually if needed
	isActive := c.FormValue("is_active") == "on"
	setting.IsActive = isActive

	// Handle Image Uploads for Settings
	// 1. White Logo (w_logo)
	wWidth, _ := strconv.Atoi(c.FormValue("w_width"))
	wHeight, _ := strconv.Atoi(c.FormValue("w_height"))
	wQuality, _ := strconv.Atoi(c.FormValue("w_quality"))
	wFormat := c.FormValue("w_format")

	wLogoPath, err := services.ProcessAndSaveImage(c, "w_logo", services.ImageOptions{
		Width:   wWidth,
		Height:  wHeight,
		Quality: wQuality,
		Format:  wFormat,
		Folder:  "settings",
	})
	if err == nil && wLogoPath != "" {
		setting.WLogo = wLogoPath
	} else {
		// Yeni dosya yoksa/başarısızsa eski logoyu koru
		setting.WLogo = oldWLogo
	}

	// 2. Black Logo (b_logo)
	bWidth, _ := strconv.Atoi(c.FormValue("b_width"))
	bHeight, _ := strconv.Atoi(c.FormValue("b_height"))
	bQuality, _ := strconv.Atoi(c.FormValue("b_quality"))
	bFormat := c.FormValue("b_format")

	bLogoPath, err := services.ProcessAndSaveImage(c, "b_logo", services.ImageOptions{
		Width:   bWidth,
		Height:  bHeight,
		Quality: bQuality,
		Format:  bFormat,
		Folder:  "settings",
	})
	if err == nil && bLogoPath != "" {
		setting.BLogo = bLogoPath
	} else {
		// Yeni dosya yoksa/başarısızsa eski logoyu koru
		setting.BLogo = oldBLogo
	}

	if err := dbConfig.DB.Save(&setting).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("<div class='alert alert-danger'>Ayarlar kaydedilirken hata oluştu.</div>")
	}

	return c.Redirect().To("/admin/content/settings?success=Ayarlar+kaydedildi")
}

// AdminCorsWhitelistCreate handles creating a new CORS whitelist entry
func AdminCorsWhitelistCreate(c fiber.Ctx) error {
	origin := c.FormValue("origin")
	description := c.FormValue("description")
	isActive := c.FormValue("is_active") == "on"

	if origin == "" {
		return c.Redirect().To("/admin/content/settings?error=Origin+gerekiyor")
	}

	entry := models.CorsWhitelist{
		Origin:      origin,
		Description: description,
		IsActive:    isActive,
	}
	if err := dbConfig.DB.Create(&entry).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Oluşturma+başarısız")
	}
	return c.Redirect().To("/admin/content/settings?success=Whitelist+eklendi")
}

// AdminCorsWhitelistDelete soft-deletes a whitelist entry
func AdminCorsWhitelistDelete(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Delete(&models.CorsWhitelist{}, id).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Silme+başarısız")
	}
	return c.Redirect().To("/admin/content/settings?success=Whitelist+silindi")
}

// AdminCorsBlacklistCreate handles creating a new CORS blacklist entry
func AdminCorsBlacklistCreate(c fiber.Ctx) error {
	origin := c.FormValue("origin")
	reason := c.FormValue("reason")
	isActive := c.FormValue("is_active") == "on"

	if origin == "" {
		return c.Redirect().To("/admin/content/settings?error=Origin+gerekiyor")
	}

	entry := models.CorsBlacklist{
		Origin:   origin,
		Reason:   reason,
		IsActive: isActive,
	}
	if err := dbConfig.DB.Create(&entry).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Oluşturma+başarısız")
	}
	return c.Redirect().To("/admin/content/settings?success=Blacklist+eklendi")
}

// AdminCorsBlacklistDelete soft-deletes a blacklist entry
func AdminCorsBlacklistDelete(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Delete(&models.CorsBlacklist{}, id).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Silme+başarısız")
	}
	return c.Redirect().To("/admin/content/settings?success=Blacklist+silindi")
}

// AdminRateLimitCreate creates a rate limit setting
func AdminRateLimitCreate(c fiber.Ctx) error {
	name := c.FormValue("name")
	description := c.FormValue("description")
	maxReq := c.FormValue("max_requests")
	window := c.FormValue("window_seconds")
	isActive := c.FormValue("is_active") == "on"

	if name == "" || maxReq == "" || window == "" {
		return c.Redirect().To("/admin/content/settings?error=Eksik+alan")
	}
	maxI, _ := strconv.ParseInt(maxReq, 10, 64)
	winI, _ := strconv.Atoi(window)

	rl := models.RateLimitSetting{
		Name:          name,
		Description:   description,
		MaxRequests:   maxI,
		WindowSeconds: winI,
		IsActive:      isActive,
	}
	if err := dbConfig.DB.Create(&rl).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Oluşturma+başarısız")
	}
	return c.Redirect().To("/admin/content/settings?success=Rate+limit+eklendi")
}

// AdminRateLimitDelete deletes a rate limit setting
func AdminRateLimitDelete(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Delete(&models.RateLimitSetting{}, id).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Silme+başarısız")
	}
	return c.Redirect().To("/admin/content/settings?success=Rate+limit+silindi")
}

// AdminCorsWhitelistRestore restores a soft-deleted whitelist entry
func AdminCorsWhitelistRestore(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Unscoped().Model(&models.CorsWhitelist{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Geri+yükleme+başarısız")
	}
	return c.Redirect().To("/admin/content/settings?success=Whitelist+geri+yüklendi")
}

// AdminCorsBlacklistRestore restores a soft-deleted blacklist entry
func AdminCorsBlacklistRestore(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Unscoped().Model(&models.CorsBlacklist{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Geri+yükleme+başarısız")
	}
	return c.Redirect().To("/admin/content/settings?success=Blacklist+geri+yüklendi")
}

// AdminRateLimitRestore restores a soft-deleted rate limit entry
func AdminRateLimitRestore(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Unscoped().Model(&models.RateLimitSetting{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Geri+yükleme+başarısız")
	}
	return c.Redirect().To("/admin/content/settings?success=Rate+limit+geri+yüklendi")
}

// AdminCorsWhitelistUpdate updates an existing whitelist entry
func AdminCorsWhitelistUpdate(c fiber.Ctx) error {
	id := c.Params("id")
	var entry models.CorsWhitelist
	if err := dbConfig.DB.First(&entry, id).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Whitelist+bulunamadı")
	}

	entry.Origin = c.FormValue("origin")
	entry.Description = c.FormValue("description")
	entry.IsActive = c.FormValue("is_active") == "on"

	if err := dbConfig.DB.Save(&entry).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Güncelleme+başarısız")
	}
	return c.Redirect().To("/admin/content/settings?success=Whitelist+güncellendi")
}

// AdminCorsBlacklistUpdate updates an existing blacklist entry
func AdminCorsBlacklistUpdate(c fiber.Ctx) error {
	id := c.Params("id")
	var entry models.CorsBlacklist
	if err := dbConfig.DB.First(&entry, id).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Blacklist+bulunamadı")
	}

	entry.Origin = c.FormValue("origin")
	entry.Reason = c.FormValue("reason")
	entry.IsActive = c.FormValue("is_active") == "on"

	if err := dbConfig.DB.Save(&entry).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Güncelleme+başarısız")
	}
	return c.Redirect().To("/admin/content/settings?success=Blacklist+güncellendi")
}

// AdminRateLimitUpdate updates an existing rate limit entry
func AdminRateLimitUpdate(c fiber.Ctx) error {
	id := c.Params("id")
	var rl models.RateLimitSetting
	if err := dbConfig.DB.First(&rl, id).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Rate+limit+bulunamadı")
	}

	rl.Name = c.FormValue("name")
	rl.Description = c.FormValue("description")
	maxReq, _ := strconv.ParseInt(c.FormValue("max_requests"), 10, 64)
	win, _ := strconv.Atoi(c.FormValue("window_seconds"))
	rl.MaxRequests = maxReq
	rl.WindowSeconds = win
	rl.IsActive = c.FormValue("is_active") == "on"

	if err := dbConfig.DB.Save(&rl).Error; err != nil {
		return c.Redirect().To("/admin/content/settings?error=Güncelleme+başarısız")
	}
	return c.Redirect().To("/admin/content/settings?success=Rate+limit+güncellendi")
}

// --- Hero Management ---

// AdminHeroNew renders the create hero form
func AdminHeroNew(c fiber.Ctx) error {
	return c.Render("admin/pages/hero_form", fiber.Map{
		"IsEdit": false,
	}, "admin/layout")
}

// AdminHeroCreate handles hero creation
func AdminHeroCreate(c fiber.Ctx) error {
	hero := new(models.Hero)
	if err := c.Bind().Body(hero); err != nil {
		return c.Status(fiber.StatusBadRequest).SendString("Geçersiz veri")
	}

	// Checkbox handling
	isActive := c.FormValue("is_active") == "on"
	hero.IsActive = isActive

	// Image Upload
	width, _ := strconv.Atoi(c.FormValue("width"))
	height, _ := strconv.Atoi(c.FormValue("height"))
	quality, _ := strconv.Atoi(c.FormValue("quality"))
	format := c.FormValue("format")

	imagePath, err := services.ProcessAndSaveImage(c, "image", services.ImageOptions{
		Width:   width,
		Height:  height,
		Quality: quality,
		Format:  format,
		Folder:  "heroes",
	})
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("Resim yükleme hatası: " + err.Error())
	}
	// If image uploaded, set it. For create, it's usually required or optimal.
	if imagePath != "" {
		hero.Image = imagePath
	}

	if err := dbConfig.DB.Create(hero).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("Oluşturma hatası: " + err.Error())
	}

	return c.Redirect().To("/admin/content/settings?success=Banner+oluşturuldu")
}

// AdminHeroEdit renders the edit hero form
func AdminHeroEdit(c fiber.Ctx) error {
	id := c.Params("id")
	var hero models.Hero
	if err := dbConfig.DB.First(&hero, id).Error; err != nil {
		return c.Status(fiber.StatusNotFound).SendString("Banner bulunamadı")
	}

	return c.Render("admin/pages/hero_form", fiber.Map{
		"IsEdit": true,
		"Hero":   hero,
	}, "admin/layout")
}

// AdminHeroUpdate handles hero update
func AdminHeroUpdate(c fiber.Ctx) error {
	id := c.Params("id")
	var hero models.Hero
	if err := dbConfig.DB.First(&hero, id).Error; err != nil {
		return c.Status(fiber.StatusNotFound).SendString("Banner bulunamadı")
	}

	// Text and numeric fields: update from form values without wiping existing data if empty
	if v := c.FormValue("color"); v != "" {
		hero.Color = v
	}
	if v := c.FormValue("title"); v != "" {
		hero.Title = v
	}
	if v := c.FormValue("text1"); v != "" {
		hero.Text1 = v
	}
	if v := c.FormValue("text2"); v != "" {
		hero.Text2 = v
	}
	if v := c.FormValue("text4"); v != "" {
		hero.Text4 = v
	}
	if v := c.FormValue("text5"); v != "" {
		hero.Text5 = v
	}

	// Checkbox handling
	isActive := c.FormValue("is_active") == "on"
	hero.IsActive = isActive

	// Image Upload (Update if new file provided)
	// Width/height/quality/format: only override if provided, otherwise keep existing values
	var width = hero.Width
	var height = hero.Height
	var quality = hero.Quality
	var format = hero.Format

	if v := c.FormValue("width"); v != "" {
		if parsed, err := strconv.Atoi(v); err == nil {
			width = parsed
			hero.Width = parsed
		}
	}
	if v := c.FormValue("height"); v != "" {
		if parsed, err := strconv.Atoi(v); err == nil {
			height = parsed
			hero.Height = parsed
		}
	}
	if v := c.FormValue("quality"); v != "" {
		if parsed, err := strconv.Atoi(v); err == nil {
			quality = parsed
			hero.Quality = parsed
		}
	}
	if v := c.FormValue("format"); v != "" {
		format = v
		hero.Format = v
	}

	imagePath, err := services.ProcessAndSaveImage(c, "image", services.ImageOptions{
		Width:   width,
		Height:  height,
		Quality: quality,
		Format:  format,
		Folder:  "heroes",
	})
	if err == nil && imagePath != "" {
		hero.Image = imagePath
	}

	if err := dbConfig.DB.Save(&hero).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("Güncelleme hatası")
	}

	return c.Redirect().To("/admin/content/settings?success=Banner+güncellendi")
}

// AdminHeroDelete handles hero soft delete
func AdminHeroDelete(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Delete(&models.Hero{}, id).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("Silme hatası")
	}
	return c.Redirect().To("/admin/content/settings?success=Banner+silindi")
}

// AdminHeroRestore restores a soft-deleted hero
func AdminHeroRestore(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Unscoped().Model(&models.Hero{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("Geri yükleme hatası")
	}
	return c.Redirect().To("/admin/content/settings?deleted=true&success=Banner+geri+yüklendi")
}

// --- Category (Blog) Management for Admin ---

// AdminContentCategories renders category list (HTMX-aware)
func AdminContentCategories(c fiber.Ctx) error {
	page, _ := strconv.Atoi(c.Query("page", "1"))
	limit := 20
	offset := (page - 1) * limit
	search := c.Query("search", "")
	showDeleted := c.Query("deleted") == "true"

	var categories []models.Category
	var total int64

	query := dbConfig.DB.Model(&models.Category{})
	if showDeleted {
		query = query.Unscoped().Where("deleted_at IS NOT NULL")
	}
	if search != "" {
		query = query.Where("title LIKE ? OR slug LIKE ?", "%"+search+"%", "%"+search+"%")
	}
	query.Count(&total)
	// preload Parent so templates can display parent title
	query.Preload("Parent").Order("created_at desc").Limit(limit).Offset(offset).Find(&categories)

	totalPages := int(math.Ceil(float64(total) / float64(limit)))

	data := fiber.Map{
		"Categories":  categories,
		"Page":        page,
		"TotalPages":  totalPages,
		"NextPage":    page + 1,
		"PrevPage":    page - 1,
		"Search":      search,
		"ShowDeleted": showDeleted,
	}

	if c.Get("HX-Request") == "true" {
		return c.Render("admin/partials/categories", data)
	}
	return c.Render("admin/partials/categories", data, "admin/layout")
}

// AdminCategoryNew renders create form
func AdminCategoryNew(c fiber.Ctx) error {
	// load possible parents
	var parents []models.Category
	dbConfig.DB.Order("title asc").Find(&parents)
	return c.Render("admin/pages/category_form", fiber.Map{
		"IsEdit":  false,
		"Parents": parents,
	}, "admin/layout")
}

// AdminCategoryCreate handles creation
func AdminCategoryCreate(c fiber.Ctx) error {
	cat := models.Category{}
	cat.Title = c.FormValue("title")

	// Generate or sanitize slug
	rawSlug := c.FormValue("slug")
	if rawSlug == "" {
		rawSlug = utils.Slugify(cat.Title)
	} else {
		rawSlug = utils.Slugify(rawSlug)
	}
	// ensure uniqueness
	attempt := rawSlug
	i := 1

	for {
		var existing models.Category
		if err := dbConfig.DB.Unscoped().Where("slug = ?", attempt).First(&existing).Error; err != nil {
			break
		}
		attempt = fmt.Sprintf("%s-%d", rawSlug, i)
		i++
	}
	cat.Slug = attempt

	if cat.Title == "" || cat.Slug == "" {
		return c.Redirect().To("/admin/content/categories?error=Başlık+ve+slug+gerekli")
	}
	if err := dbConfig.DB.Create(&cat).Error; err != nil {
		return c.Redirect().To("/admin/content/categories?error=Oluşturma+başarısız")
	}
	return c.Redirect().To("/admin/content/categories?success=Kategori+eklendi")
}

// AdminCategoryEdit renders edit form
func AdminCategoryEdit(c fiber.Ctx) error {
	id := c.Params("id")
	var cat models.Category
	if err := dbConfig.DB.First(&cat, id).Error; err != nil {
		return c.Status(fiber.StatusNotFound).SendString("Kategori bulunamadı")
	}
	var parents []models.Category
	dbConfig.DB.Where("id != ?", cat.ID).Order("title asc").Find(&parents)
	// pass parent id value for easier template comparison
	var parentID uint = 0
	if cat.ParentID != nil {
		parentID = *cat.ParentID
	}
	return c.Render("admin/pages/category_form", fiber.Map{
		"IsEdit":   true,
		"Category": cat,
		"Parents":  parents,
		"ParentID": parentID,
	}, "admin/layout")
}

// AdminCategoryUpdate handles update
func AdminCategoryUpdate(c fiber.Ctx) error {
	id := c.Params("id")
	var cat models.Category
	if err := dbConfig.DB.First(&cat, id).Error; err != nil {
		return c.Redirect().To("/admin/content/categories?error=Kategori+bulunamadı")
	}
	cat.Title = c.FormValue("title")
	// sanitize/generate slug; allow keeping unique (exclude current record)
	rawSlug := c.FormValue("slug")
	if rawSlug == "" {
		rawSlug = utils.Slugify(cat.Title)
	} else {
		rawSlug = utils.Slugify(rawSlug)
	}
	attempt := rawSlug
	i := 1
	for {
		var existing models.Category
		if err := dbConfig.DB.Unscoped().Where("slug = ? AND id != ?", attempt, cat.ID).First(&existing).Error; err != nil {
			break
		}
		attempt = fmt.Sprintf("%s-%d", rawSlug, i)
		i++
	}
	cat.Slug = attempt
	cat.Description = c.FormValue("description")
	if pid := c.FormValue("parent_id"); pid != "" {
		if v, err := strconv.ParseUint(pid, 10, 64); err == nil {
			p := uint(v)
			cat.ParentID = &p
		}
	} else {
		cat.ParentID = nil
	}

	if err := dbConfig.DB.Save(&cat).Error; err != nil {
		return c.Redirect().To("/admin/content/categories?error=Güncelleme+başarısız")
	}
	return c.Redirect().To("/admin/content/categories?success=Kategori+güncellendi")
}

// AdminCategoryDelete soft-delete
func AdminCategoryDelete(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Delete(&models.Category{}, id).Error; err != nil {
		return c.Redirect().To("/admin/content/categories?error=Silme+başarısız")
	}
	return c.Redirect().To("/admin/content/categories?success=Kategori+silindi")
}

// AdminCategoryRestore restores soft-deleted
func AdminCategoryRestore(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Unscoped().Model(&models.Category{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
		return c.Redirect().To("/admin/content/categories?error=Geri+yükleme+başarısız")
	}
	return c.Redirect().To("/admin/content/categories?deleted=true&success=Kategori+geri+yüklendi")
}

// AdminContentCategoryViews lists category view records
func AdminContentCategoryViews(c fiber.Ctx) error {
	page, _ := strconv.Atoi(c.Query("page", "1"))
	limit := 20
	offset := (page - 1) * limit
	search := c.Query("search", "")
	showDeleted := c.Query("deleted") == "true"

	var views []models.CategoryView
	var total int64

	query := dbConfig.DB.Model(&models.CategoryView{})
	if showDeleted {
		query = query.Unscoped().Where("deleted_at IS NOT NULL")
	}
	if search != "" {
		query = query.Where("ip_address LIKE ?", "%"+search+"%")
	}
	query.Count(&total)
	query.Order("created_at desc").Limit(limit).Offset(offset).Find(&views)

	// build map of category titles
	catIDs := make([]uint, 0)
	for _, v := range views {
		if v.CategoryID != 0 {
			catIDs = append(catIDs, v.CategoryID)
		}
	}
	var cats []models.Category
	if len(catIDs) > 0 {
		dbConfig.DB.Where("id IN ?", catIDs).Find(&cats)
	}
	catMap := make(map[uint]string)
	for _, c := range cats {
		catMap[c.ID] = c.Title
	}

	totalPages := int(math.Ceil(float64(total) / float64(limit)))

	data := fiber.Map{
		"Views":       views,
		"CatMap":      catMap,
		"Page":        page,
		"TotalPages":  totalPages,
		"NextPage":    page + 1,
		"PrevPage":    page - 1,
		"Search":      search,
		"ShowDeleted": showDeleted,
	}

	if c.Get("HX-Request") == "true" {
		return c.Render("admin/partials/category_views", data)
	}
	return c.Render("admin/partials/category_views", data, "admin/layout")
}

// AdminCategoryViewDelete soft-delete a view record
func AdminCategoryViewDelete(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Delete(&models.CategoryView{}, id).Error; err != nil {
		return c.Redirect().To("/admin/content/category-views?error=Silme+başarısız")
	}
	return c.Redirect().To("/admin/content/category-views?success=Kayıt+silindi")
}

// AdminCategoryViewRestore restores soft-deleted view record
func AdminCategoryViewRestore(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Unscoped().Model(&models.CategoryView{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
		return c.Redirect().To("/admin/content/category-views?error=Geri+yükleme+başarısız")
	}
	return c.Redirect().To("/admin/content/category-views?deleted=true&success=Kayıt+geri+yüklendi")
}

// AdminContentComments lists comments
func AdminContentComments(c fiber.Ctx) error {
	page, _ := strconv.Atoi(c.Query("page", "1"))
	limit := 20
	offset := (page - 1) * limit
	search := c.Query("search", "")
	showDeleted := c.Query("deleted") == "true"

	var comments []models.Comment
	var total int64

	query := dbConfig.DB.Model(&models.Comment{})
	if showDeleted {
		query = query.Unscoped().Where("deleted_at IS NOT NULL")
	}
	if search != "" {
		query = query.Where("body LIKE ?", "%"+search+"%")
	}
	query.Count(&total)
	query.Order("created_at desc").Limit(limit).Offset(offset).Find(&comments)

	totalPages := int(math.Ceil(float64(total) / float64(limit)))

	data := fiber.Map{
		"Comments":    comments,
		"Page":        page,
		"TotalPages":  totalPages,
		"NextPage":    page + 1,
		"PrevPage":    page - 1,
		"Search":      search,
		"ShowDeleted": showDeleted,
	}

	if c.Get("HX-Request") == "true" {
		return c.Render("admin/partials/comments", data)
	}
	return c.Render("admin/partials/comments", data, "admin/layout")
}

// AdminCommentEdit renders edit form for a comment
func AdminCommentEdit(c fiber.Ctx) error {
	id := c.Params("id")
	var comment models.Comment
	if err := dbConfig.DB.First(&comment, id).Error; err != nil {
		return c.Status(fiber.StatusNotFound).SendString("Yorum bulunamadı")
	}
	return c.Render("admin/pages/comment_form", fiber.Map{"IsEdit": true, "Comment": comment}, "admin/layout")
}

// AdminCommentUpdate updates a comment
func AdminCommentUpdate(c fiber.Ctx) error {
	id := c.Params("id")
	var comment models.Comment
	if err := dbConfig.DB.First(&comment, id).Error; err != nil {
		return c.Redirect().To("/admin/content/comments?error=Yorum+bulunamadı")
	}
	comment.Body = c.FormValue("body")
	if err := dbConfig.DB.Save(&comment).Error; err != nil {
		return c.Redirect().To("/admin/content/comments?error=Güncelleme+başarısız")
	}
	return c.Redirect().To("/admin/content/comments?success=Yorum+güncellendi")
}

// AdminCommentDelete soft-delete
func AdminCommentDelete(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Delete(&models.Comment{}, id).Error; err != nil {
		return c.Redirect().To("/admin/content/comments?error=Silme+başarısız")
	}
	return c.Redirect().To("/admin/content/comments?success=Yorum+silindi")
}

// AdminCommentRestore restores a soft-deleted comment
func AdminCommentRestore(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Unscoped().Model(&models.Comment{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
		return c.Redirect().To("/admin/content/comments?error=Geri+yükleme+başarısız")
	}
	return c.Redirect().To("/admin/content/comments?deleted=true&success=Yorum+geri+yüklendi")
}

// --- Tag Management (Admin) ---

// AdminContentTags renders tag list (HTMX-aware)
func AdminContentTags(c fiber.Ctx) error {
	page, _ := strconv.Atoi(c.Query("page", "1"))
	limit := 20
	offset := (page - 1) * limit
	search := c.Query("search", "")
	showDeleted := c.Query("deleted") == "true"

	var tags []models.Tag
	var total int64

	query := dbConfig.DB.Model(&models.Tag{})
	if showDeleted {
		query = query.Unscoped().Where("deleted_at IS NOT NULL")
	}
	if search != "" {
		query = query.Where("name LIKE ?", "%"+search+"%")
	}
	query.Count(&total)
	query.Order("created_at desc").Limit(limit).Offset(offset).Find(&tags)

	totalPages := int(math.Ceil(float64(total) / float64(limit)))

	data := fiber.Map{
		"Tags":        tags,
		"Page":        page,
		"TotalPages":  totalPages,
		"NextPage":    page + 1,
		"PrevPage":    page - 1,
		"Search":      search,
		"ShowDeleted": showDeleted,
	}

	if c.Get("HX-Request") == "true" {
		return c.Render("admin/partials/tags", data)
	}
	return c.Render("admin/partials/tags", data, "admin/layout")
}

// AdminTagNew renders create form
func AdminTagNew(c fiber.Ctx) error {
	return c.Render("admin/pages/tag_form", fiber.Map{"IsEdit": false}, "admin/layout")
}

// AdminTagCreate handles tag creation
func AdminTagCreate(c fiber.Ctx) error {
	name := c.FormValue("name")
	if name == "" {
		return c.Redirect().To("/admin/content/tags?error=İsim+gerekli")
	}
	tag := models.Tag{Name: name}
	if err := dbConfig.DB.Create(&tag).Error; err != nil {
		return c.Redirect().To("/admin/content/tags?error=Oluşturma+başarısız")
	}
	return c.Redirect().To("/admin/content/tags?success=Tag+eklendi")
}

// AdminTagEdit renders edit form
func AdminTagEdit(c fiber.Ctx) error {
	id := c.Params("id")
	var tag models.Tag
	if err := dbConfig.DB.First(&tag, id).Error; err != nil {
		return c.Status(fiber.StatusNotFound).SendString("Tag bulunamadı")
	}
	return c.Render("admin/pages/tag_form", fiber.Map{"IsEdit": true, "Tag": tag}, "admin/layout")
}

// AdminTagUpdate handles update
func AdminTagUpdate(c fiber.Ctx) error {
	id := c.Params("id")
	var tag models.Tag
	if err := dbConfig.DB.First(&tag, id).Error; err != nil {
		return c.Redirect().To("/admin/content/tags?error=Tag+bulunamadı")
	}
	tag.Name = c.FormValue("name")
	if tag.Name == "" {
		return c.Redirect().To("/admin/content/tags?error=İsim+gerekli")
	}
	if err := dbConfig.DB.Save(&tag).Error; err != nil {
		return c.Redirect().To("/admin/content/tags?error=Güncelleme+başarısız")
	}
	return c.Redirect().To("/admin/content/tags?success=Tag+güncellendi")
}

// AdminTagDelete soft-delete
func AdminTagDelete(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Delete(&models.Tag{}, id).Error; err != nil {
		return c.Redirect().To("/admin/content/tags?error=Silme+başarısız")
	}
	return c.Redirect().To("/admin/content/tags?success=Tag+silindi")
}

// AdminTagRestore restores soft-deleted tag
func AdminTagRestore(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Unscoped().Model(&models.Tag{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
		return c.Redirect().To("/admin/content/tags?error=Geri+yükleme+başarısız")
	}
	return c.Redirect().To("/admin/content/tags?deleted=true&success=Tag+geri+yüklendi")
}

// --- Post Management (Admin) ---

// AdminContentPosts renders posts list (HTMX-aware)
func AdminContentPosts(c fiber.Ctx) error {
	page, _ := strconv.Atoi(c.Query("page", "1"))
	limit := 20
	offset := (page - 1) * limit
	search := c.Query("search", "")
	showDeleted := c.Query("deleted") == "true"

	var posts []models.Post
	var total int64

	query := dbConfig.DB.Model(&models.Post{})
	if showDeleted {
		query = query.Unscoped().Where("deleted_at IS NOT NULL")
	}
	if search != "" {
		query = query.Where("title LIKE ? OR slug LIKE ?", "%"+search+"%", "%"+search+"%")
	}
	query.Count(&total)
	query.Preload("Categories").Preload("Tags").Order("created_at desc").Limit(limit).Offset(offset).Find(&posts)

	// build first-image map for templates (posts.Images is stored as JSON array string)
	imageMap := make(map[uint]string)
	for _, p := range posts {
		if p.Images != "" {
			imgs := parseImagesField(p.Images)
			if len(imgs) > 0 {
				imageMap[p.ID] = imgs[0]
			}
		}
	}

	totalPages := int(math.Ceil(float64(total) / float64(limit)))

	data := fiber.Map{
		"Posts":       posts,
		"ImageMap":    imageMap,
		"Page":        page,
		"TotalPages":  totalPages,
		"NextPage":    page + 1,
		"PrevPage":    page - 1,
		"Search":      search,
		"ShowDeleted": showDeleted,
		"Success":     c.Query("success"),
		"Error":       c.Query("error"),
	}

	if c.Get("HX-Request") == "true" {
		return c.Render("admin/partials/posts", data)
	}
	return c.Render("admin/partials/posts", data, "admin/layout")
}

// AdminPostNew renders create form
func AdminPostNew(c fiber.Ctx) error {
	var cats []models.Category
	var tags []models.Tag
	dbConfig.DB.Order("title asc").Find(&cats)
	dbConfig.DB.Order("name asc").Find(&tags)
	return c.Render("admin/pages/post_form", fiber.Map{"IsEdit": false, "Categories": cats, "Tags": tags, "FirstImage": ""}, "admin/layout")
}

// AdminFetchImage downloads a remote image URL, processes it and returns saved path.
func AdminFetchImage(c fiber.Ctx) error {
	var payload struct {
		Url     string `json:"url"`
		Width   int    `json:"width"`
		Height  int    `json:"height"`
		Quality int    `json:"quality"`
		Format  string `json:"format"`
	}
	// Parse JSON body (use json.Unmarshal on raw body for compatibility)
	if err := json.Unmarshal(c.Body(), &payload); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid payload"})
	}
	if payload.Url == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "url required"})
	}

	// Fetch remote image with timeout and size limit
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Get(payload.Url)
	if err != nil {
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": "failed to fetch url"})
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": "failed to fetch url"})
	}
	ct := resp.Header.Get("Content-Type")
	if ct == "" || !strings.HasPrefix(ct, "image/") {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "url is not an image"})
	}
	// limit read to 6MB
	reader := io.LimitReader(resp.Body, 6*1024*1024)
	data, err := io.ReadAll(reader)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "could not read image"})
	}

	saved, err := services.ProcessAndSaveImageFromBytes(data, services.ImageOptions{
		Width:   payload.Width,
		Height:  payload.Height,
		Quality: payload.Quality,
		Format:  payload.Format,
		Folder:  "posts",
	})
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "could not process image"})
	}
	return c.JSON(fiber.Map{"url": saved})
}

// AdminPostCreate handles creation
func AdminPostCreate(c fiber.Ctx) error {
	configs.Logger.Info(
		"AdminPostCreate called",
		zap.String("method", c.Method()),
		zap.String("path", c.Path()),
		zap.String("content_type", c.Get("Content-Type")),
	)

	title := c.FormValue("title")
	if title == "" {
		return c.Redirect().To("/admin/content/posts?error=Başlık+gerekli")
	}
	post := models.Post{Title: title}
	post.Content = c.FormValue("content")

	// Slug handling
	rawSlug := c.FormValue("slug")
	if rawSlug == "" {
		rawSlug = utils.Slugify(post.Title)
	} else {
		rawSlug = utils.Slugify(rawSlug)
	}
	attempt := rawSlug
	i := 1
	for {
		var existing models.Post
		err := dbConfig.DB.Unscoped().Where("slug = ?", attempt).First(&existing).Error
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				break
			}
			return c.Redirect().To("/admin/content/posts?error=DB+error")
		}
		attempt = fmt.Sprintf("%s-%d", rawSlug, i)
		i++
	}
	post.Slug = attempt

	// Categories
	catIDs := c.FormValue("category_ids")
	if catIDs != "" {
		ids := parseIDsCSV(catIDs)
		if len(ids) > 0 {
			var cats []models.Category
			dbConfig.DB.Find(&cats, ids)
			post.Categories = cats
		}
	}
	// Tags
	tagIDs := c.FormValue("tag_ids")
	if tagIDs != "" {
		ids := parseIDsCSV(tagIDs)
		if len(ids) > 0 {
			var tags []models.Tag
			dbConfig.DB.Find(&tags, ids)
			post.Tags = tags
		}
	}

	// Image processing using service
	width, _ := strconv.Atoi(c.FormValue("width"))
	height, _ := strconv.Atoi(c.FormValue("height"))
	quality, _ := strconv.Atoi(c.FormValue("quality"))
	format := c.FormValue("format")

	imagePath, err := services.ProcessAndSaveImage(c, "image", services.ImageOptions{
		Width:   width,
		Height:  height,
		Quality: quality,
		Format:  format,
		Folder:  "posts",
	})
	if err == nil && imagePath != "" {
		// Ana görüntü
		post.Images = imagePath
		post.Width = width
		post.Height = height
		post.Quality = quality
		if format != "" {
			post.Format = format
		}

		// Orta boy (400x300)
		midPath, errMid := services.ProcessAndSaveImage(c, "image", services.ImageOptions{
			Width:   400,
			Height:  300,
			Quality: quality,
			Format:  format,
			Folder:  "posts",
		})
		if errMid != nil {
			return c.Redirect().To("/admin/content/posts?error=Orta+boy+resim+oluşturulamadı")
		}
		post.ImagesMid = midPath

		// Küçük boy (48x48)
		minPath, errMin := services.ProcessAndSaveImage(c, "image", services.ImageOptions{
			Width:   48,
			Height:  48,
			Quality: quality,
			Format:  format,
			Folder:  "posts",
		})
		if errMin != nil {
			return c.Redirect().To("/admin/content/posts?error=Küçük+boy+resim+oluşturulamadı")
		}
		post.ImagesMin = minPath
	} else if err != nil {
		configs.Logger.Error("AdminPostCreate image upload failed", zap.Error(err))
		return c.Redirect().To("/admin/content/posts?error=Image+API+key+gecersiz+veya+suresi+dolmus")
	} else {
		configs.Logger.Warn("AdminPostCreate image upload skipped or empty result")
	}

	if err := dbConfig.DB.Create(&post).Error; err != nil {
		return c.Redirect().To("/admin/content/posts?error=Oluşturma+başarısız")
	}
	return c.Redirect().To("/admin/content/posts?success=Yazı+eklendi")
}

// AdminPostEdit renders edit form
func AdminPostEdit(c fiber.Ctx) error {
	id := c.Params("id")
	var post models.Post
	if err := dbConfig.DB.Preload("Categories").Preload("Tags").First(&post, id).Error; err != nil {
		return c.Status(fiber.StatusNotFound).SendString("Yazı bulunamadı")
	}
	var cats []models.Category
	var tags []models.Tag
	dbConfig.DB.Order("title asc").Find(&cats)
	dbConfig.DB.Order("name asc").Find(&tags)
	// extract first image if present (support plain string or JSON array)
	firstImage := ""
	if post.Images != "" {
		imgs := parseImagesField(post.Images)
		if len(imgs) > 0 {
			firstImage = imgs[0]
		}
	}
	return c.Render("admin/pages/post_form", fiber.Map{"IsEdit": true, "Post": post, "Categories": cats, "Tags": tags, "FirstImage": firstImage}, "admin/layout")
}

// AdminPostUpdate handles update
func AdminPostUpdate(c fiber.Ctx) error {
	configs.Logger.Info(
		"AdminPostUpdate called",
		zap.String("method", c.Method()),
		zap.String("path", c.Path()),
		zap.String("content_type", c.Get("Content-Type")),
	)

	id := c.Params("id")
	var post models.Post
	if err := dbConfig.DB.Preload("Categories").Preload("Tags").First(&post, id).Error; err != nil {
		return c.Redirect().To("/admin/content/posts?error=Yazı+bulunamadı")
	}
	title := c.FormValue("title")
	if title != "" {
		post.Title = title
		rawSlug := c.FormValue("slug")
		if rawSlug == "" {
			rawSlug = utils.Slugify(post.Title)
		} else {
			rawSlug = utils.Slugify(rawSlug)
		}
		attempt := rawSlug
		i := 1
		for {
			var existing models.Post
			err := dbConfig.DB.Unscoped().Where("slug = ? AND id != ?", attempt, post.ID).First(&existing).Error
			if err != nil {
				if errors.Is(err, gorm.ErrRecordNotFound) {
					break
				}
				return c.Redirect().To("/admin/content/posts?error=DB+error")
			}
			attempt = fmt.Sprintf("%s-%d", rawSlug, i)
			i++
		}
		post.Slug = attempt
	}
	if content := c.FormValue("content"); content != "" {
		post.Content = content
	}

	// Categories
	if catIDs := c.FormValue("category_ids"); catIDs != "" {
		ids := parseIDsCSV(catIDs)
		if len(ids) > 0 {
			var cats []models.Category
			dbConfig.DB.Find(&cats, ids)
			if err := dbConfig.DB.Model(&post).Association("Categories").Replace(&cats); err != nil {
				return c.Redirect().To("/admin/content/posts?error=Kategori+güncelleme+başarısız")
			}
		}
	}
	// Tags
	if tagIDs := c.FormValue("tag_ids"); tagIDs != "" {
		ids := parseIDsCSV(tagIDs)
		if len(ids) > 0 {
			var tags []models.Tag
			dbConfig.DB.Find(&tags, ids)
			if err := dbConfig.DB.Model(&post).Association("Tags").Replace(&tags); err != nil {
				return c.Redirect().To("/admin/content/posts?error=Tag+güncelleme+başarısız")
			}
		}
	}

	// Image processing
	width, _ := strconv.Atoi(c.FormValue("width"))
	height, _ := strconv.Atoi(c.FormValue("height"))
	quality, _ := strconv.Atoi(c.FormValue("quality"))
	format := c.FormValue("format")

	imagePath, err := services.ProcessAndSaveImage(c, "image", services.ImageOptions{
		Width:   width,
		Height:  height,
		Quality: quality,
		Format:  format,
		Folder:  "posts",
	})
	if err == nil && imagePath != "" {
		// Ana görüntü
		post.Images = imagePath
		post.Width = width
		post.Height = height
		post.Quality = quality
		if format != "" {
			post.Format = format
		}

		// Orta boy (400x300)
		midPath, errMid := services.ProcessAndSaveImage(c, "image", services.ImageOptions{
			Width:   400,
			Height:  300,
			Quality: quality,
			Format:  format,
			Folder:  "posts",
		})
		if errMid != nil {
			return c.Redirect().To("/admin/content/posts?error=Orta+boy+resim+oluşturulamadı")
		}
		post.ImagesMid = midPath

		// Küçük boy (48x48)
		minPath, errMin := services.ProcessAndSaveImage(c, "image", services.ImageOptions{
			Width:   48,
			Height:  48,
			Quality: quality,
			Format:  format,
			Folder:  "posts",
		})
		if errMin != nil {
			return c.Redirect().To("/admin/content/posts?error=Küçük+boy+resim+oluşturulamadı")
		}
		post.ImagesMin = minPath
	} else if err != nil {
		configs.Logger.Error("AdminPostUpdate image upload failed", zap.Error(err))
		return c.Redirect().To("/admin/content/posts?error=Image+API+key+gecersiz+veya+suresi+dolmus")
	} else {
		configs.Logger.Warn("AdminPostUpdate image upload skipped or empty result")
	}

	if err := dbConfig.DB.Save(&post).Error; err != nil {
		return c.Redirect().To("/admin/content/posts?error=Güncelleme+başarısız")
	}
	return c.Redirect().To("/admin/content/posts?success=Yazı+güncellendi")
}

// AdminPostDelete soft-delete
func AdminPostDelete(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Delete(&models.Post{}, id).Error; err != nil {
		return c.Redirect().To("/admin/content/posts?error=Silme+başarısız")
	}
	return c.Redirect().To("/admin/content/posts?success=Yazı+silindi")
}

// AdminPostRestore restores soft-deleted post
func AdminPostRestore(c fiber.Ctx) error {
	id := c.Params("id")
	if err := dbConfig.DB.Unscoped().Model(&models.Post{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
		return c.Redirect().To("/admin/content/posts?error=Geri+yükleme+başarısız")
	}
	return c.Redirect().To("/admin/content/posts?deleted=true&success=Yazı+geri+yüklendi")
}
