package controllers

import (
	"math"
	"mime"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/gofiber/fiber/v2"
	"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
	"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
)

// helper: obtiene el provider OSS por ID o el activo por defecto
func getOSSProvider(c *fiber.Ctx) (services.OSSProvider, error) {
	idStr := c.Query("oss_api_id", "")
	if idStr == "" {
		idStr = c.FormValue("oss_api_id", "")
	}
	if idStr != "" {
		id, err := strconv.ParseUint(idStr, 10, 32)
		if err == nil && id > 0 {
			return services.NewOSSProviderByID(uint(id))
		}
	}
	return services.NewOSSProviderFromDB()
}

// OssApiIndex renderiza la vista del panel de gestión de Alibaba Cloud OSS.
func OssApiIndex(c *fiber.Ctx) error {
	data := fiber.Map{
		"user":    c.Locals("user").(map[string]interface{}),
		"modules": c.Locals("userModules"),
	}
	return c.Render("oss_api", data, "layouts/main")
}

// GetOssApiConfigs devuelve la lista paginada de configuraciones en JSON.
func GetOssApiConfigs(c *fiber.Ctx) error {
	page, _ := strconv.Atoi(c.Query("page", "1"))
	if page < 1 {
		page = 1
	}
	limit := 20
	offset := (page - 1) * limit

	items, total, err := models.GetAllOssApi(
		limit, offset,
		c.Query("search", ""),
		c.Query("provider", ""),
		c.Query("estado", ""),
		c.Query("sort_by", "id"),
		c.Query("sort_dir", "desc"),
		c.Query("date_from", ""),
		c.Query("date_to", ""),
	)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}

	return c.JSON(fiber.Map{
		"items":      items,
		"total":      total,
		"totalPages": int(math.Ceil(float64(total) / float64(limit))),
		"page":       page,
		"limit":      limit,
	})
}

// CreateOssApiConfig crea una nueva configuración de Alibaba Cloud OSS.
func CreateOssApiConfig(c *fiber.Ctx) error {
	type Req struct {
		Name            string `json:"name"`
		Provider        string `json:"provider"`
		Endpoint        string `json:"endpoint"`
		AccessKeyID     string `json:"access_key_id"`
		AccessKeySecret string `json:"access_key_secret"`
		BucketName      string `json:"bucket_name"`
		Region          string `json:"region"`
		IsActive        bool   `json:"is_active"`
		Notes           string `json:"notes"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
	}
	if strings.TrimSpace(req.Name) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "name es requerido"})
	}
	if strings.TrimSpace(req.Endpoint) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "endpoint es requerido"})
	}
	if strings.TrimSpace(req.AccessKeyID) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "access_key_id es requerido"})
	}
	if strings.TrimSpace(req.AccessKeySecret) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "access_key_secret es requerido"})
	}
	if strings.TrimSpace(req.BucketName) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "bucket_name es requerido"})
	}

	provider := strings.TrimSpace(req.Provider)
	if provider == "" {
		provider = "alibaba"
	}
	item := models.OssApi{
		Name:            strings.TrimSpace(req.Name),
		Provider:        provider,
		Endpoint:        strings.TrimSpace(req.Endpoint),
		AccessKeyID:     strings.TrimSpace(req.AccessKeyID),
		AccessKeySecret: strings.TrimSpace(req.AccessKeySecret),
		BucketName:      strings.TrimSpace(req.BucketName),
		Region:          strings.TrimSpace(req.Region),
		IsActive:        req.IsActive,
		Notes:           strings.TrimSpace(req.Notes),
	}
	if err := models.CreateOssApi(&item); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.Status(fiber.StatusCreated).JSON(fiber.Map{"ok": true, "item": item})
}

// UpdateOssApiConfig actualiza una configuración existente de Alibaba Cloud OSS.
func UpdateOssApiConfig(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 64)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "id inválido"})
	}

	existing, err := models.GetOssApiByID(uint(id))
	if err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "configuración no encontrada"})
	}

	type Req struct {
		Name            string `json:"name"`
		Provider        string `json:"provider"`
		Endpoint        string `json:"endpoint"`
		AccessKeyID     string `json:"access_key_id"`
		AccessKeySecret string `json:"access_key_secret"`
		BucketName      string `json:"bucket_name"`
		Region          string `json:"region"`
		IsActive        bool   `json:"is_active"`
		Notes           string `json:"notes"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
	}
	if strings.TrimSpace(req.Name) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "name es requerido"})
	}

	existing.Name = strings.TrimSpace(req.Name)
	existing.Provider = strings.TrimSpace(req.Provider)
	existing.Endpoint = strings.TrimSpace(req.Endpoint)
	existing.BucketName = strings.TrimSpace(req.BucketName)
	existing.Region = strings.TrimSpace(req.Region)
	existing.IsActive = req.IsActive
	existing.Notes = strings.TrimSpace(req.Notes)
	// Solo actualizar secrets si se envían valores no vacíos
	if strings.TrimSpace(req.AccessKeyID) != "" {
		existing.AccessKeyID = strings.TrimSpace(req.AccessKeyID)
	}
	if strings.TrimSpace(req.AccessKeySecret) != "" {
		existing.AccessKeySecret = strings.TrimSpace(req.AccessKeySecret)
	}

	if err := models.UpdateOssApi(existing); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true, "item": existing})
}

// GetActiveOssApiList devuelve todas las configuraciones activas (para selectores UI).
func GetActiveOssApiList(c *fiber.Ctx) error {
	items, err := models.GetActiveOssApis()
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"data": items})
}

// DeleteOssApiConfig elimina una configuración de Alibaba Cloud OSS.
func DeleteOssApiConfig(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 64)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "id inválido"})
	}

	item, err := models.GetOssApiByID(uint(id))
	if err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "configuración no encontrada"})
	}

	if err := models.DeleteOssApi(item); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

// ─── Explorador de archivos OSS ──────────────────────────────────────────────

// OssBrowserList lista los objetos del bucket activo con soporte de carpetas virtuales.
// Query params: prefix (carpeta actual), marker (paginación), max_keys
func OssBrowserList(c *fiber.Ctx) error {
	svc, err := getOSSProvider(c)
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "Sin configuración OSS activa: " + err.Error()})
	}

	prefix := c.Query("prefix", "")
	marker := c.Query("marker", "")
	maxKeys, _ := strconv.Atoi(c.Query("max_keys", "100"))

	result, err := svc.ListObjects(prefix, marker, maxKeys)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(result)
}

// OssBrowserSignedURL genera una URL firmada temporal (1 hora) para previsualizar o descargar un objeto.
// Query param: key (objeto)
func OssBrowserSignedURL(c *fiber.Ctx) error {
	key := strings.TrimSpace(c.Query("key", ""))
	if key == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "key es requerido"})
	}

	svc, err := getOSSProvider(c)
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "Sin configuración OSS activa: " + err.Error()})
	}

	url, err := svc.SignedURL(key, 3600)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"url": url})
}

// OssBrowserDelete elimina un objeto del bucket activo.
// Body JSON: { "key": "ruta/al/archivo.jpg", "oss_api_id": 1 }
func OssBrowserDelete(c *fiber.Ctx) error {
	type Req struct {
		Key string `json:"key"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil || strings.TrimSpace(req.Key) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "key es requerido"})
	}

	svc, err := getOSSProvider(c)
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "Sin configuración OSS activa: " + err.Error()})
	}

	if err := svc.DeleteFile(req.Key); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

// OssBrowserUpload sube un archivo al bucket activo.
// Form field: file (multipart), prefix (carpeta destino opcional)
func OssBrowserUpload(c *fiber.Ctx) error {
	prefix := strings.TrimSpace(c.FormValue("prefix", ""))
	fh, err := c.FormFile("file")
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "archivo requerido"})
	}

	f, err := fh.Open()
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "error abriendo archivo"})
	}
	defer f.Close()

	objectKey := fh.Filename
	if prefix != "" {
		objectKey = strings.TrimRight(prefix, "/") + "/" + fh.Filename
	}

	ct := mime.TypeByExtension(filepath.Ext(fh.Filename))
	if ct == "" {
		ct = "application/octet-stream"
	}

	svc, err := getOSSProvider(c)
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "Sin configuración OSS activa: " + err.Error()})
	}

	if err := svc.UploadFromReader(objectKey, ct, f); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true, "key": objectKey})
}
