package controllers

import (
	"math"
	"strconv"

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

// DocPaginasIndex renderiza el panel de gestión de páginas de documentación.
func DocPaginasIndex(c *fiber.Ctx) error {
	data := fiber.Map{
		"user":    c.Locals("user").(map[string]interface{}),
		"modules": c.Locals("userModules"),
	}
	if err := c.Render("docs/paginas", data, "layouts/main"); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return nil
}

// GetDocPaginas devuelve la lista paginada de páginas en JSON.
func GetDocPaginas(c *fiber.Ctx) error {
	page, _ := strconv.Atoi(c.Query("page", "1"))
	if page < 1 {
		page = 1
	}
	search := c.Query("search", "")
	limit := 10
	offset := (page - 1) * limit

	var saasID, catID uint
	if v, err := strconv.ParseUint(c.Query("saas_id", "0"), 10, 32); err == nil {
		saasID = uint(v)
	}
	if v, err := strconv.ParseUint(c.Query("categoria_id", "0"), 10, 32); err == nil {
		catID = uint(v)
	}

	items, total, err := models.GetAllDocPaginas(limit, offset, search, saasID, catID)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}

	// Selects para el formulario
	saasItems, _ := models.GetAllSaasProductosSelect()
	categorias, _ := models.GetAllDocCategoriasSelect()
	roles, _ := models.AllRolesSelect()

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

// GetDocPaginaDetalle devuelve una página por ID con todos sus datos para edición.
func GetDocPaginaDetalle(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "ID inválido"})
	}
	item, err := models.GetDocPaginaByID(uint(id))
	if err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Página no encontrada"})
	}
	return c.JSON(item)
}

// CreateDocPagina crea una nueva página de documentación.
func CreateDocPagina(c *fiber.Ctx) error {
	type Req struct {
		SaasID        uint   `json:"saas_id"`
		CategoriaID   uint   `json:"categoria_id"`
		Titulo        string `json:"titulo"`
		Slug          string `json:"slug"`
		Contenido     string `json:"contenido"`
		TipoContenido string `json:"tipo_contenido"`
		Visibilidad   string `json:"visibilidad"`
		Orden         int    `json:"orden"`
		Publicado     bool   `json:"publicado"`
		RolIDs        []uint `json:"rol_ids"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Datos inválidos"})
	}
	if req.Titulo == "" || req.Slug == "" || req.SaasID == 0 || req.CategoriaID == 0 {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Título, slug, saas y categoría son obligatorios"})
	}
	if req.TipoContenido == "" {
		req.TipoContenido = "markdown"
	}
	if req.Visibilidad == "" {
		req.Visibilidad = "public"
	}

	userMap, _ := c.Locals("user").(map[string]interface{})
	var creadoPor uint
	if userMap != nil {
		if idVal, ok := userMap["id"]; ok {
			switch v := idVal.(type) {
			case float64:
				creadoPor = uint(v)
			case uint:
				creadoPor = v
			}
		}
	}

	item := &models.DocPagina{
		SaasID:        req.SaasID,
		CategoriaID:   req.CategoriaID,
		Titulo:        req.Titulo,
		Slug:          req.Slug,
		Contenido:     req.Contenido,
		TipoContenido: req.TipoContenido,
		Visibilidad:   req.Visibilidad,
		Orden:         req.Orden,
		Publicado:     req.Publicado,
		CreadoPor:     creadoPor,
	}
	if err := models.CreateDocPagina(item, req.RolIDs); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"message": "Página creada", "item": item})
}

// UpdateDocPagina actualiza una página existente.
func UpdateDocPagina(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "ID inválido"})
	}
	type Req struct {
		SaasID        uint   `json:"saas_id"`
		CategoriaID   uint   `json:"categoria_id"`
		Titulo        string `json:"titulo"`
		Slug          string `json:"slug"`
		Contenido     string `json:"contenido"`
		TipoContenido string `json:"tipo_contenido"`
		Visibilidad   string `json:"visibilidad"`
		Orden         int    `json:"orden"`
		Publicado     bool   `json:"publicado"`
		RolIDs        []uint `json:"rol_ids"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Datos inválidos"})
	}
	item := &models.DocPagina{
		SaasID:        req.SaasID,
		CategoriaID:   req.CategoriaID,
		Titulo:        req.Titulo,
		Slug:          req.Slug,
		Contenido:     req.Contenido,
		TipoContenido: req.TipoContenido,
		Visibilidad:   req.Visibilidad,
		Orden:         req.Orden,
		Publicado:     req.Publicado,
	}
	item.ID = uint(id)
	if err := models.UpdateDocPagina(item, req.RolIDs); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"message": "Página actualizada"})
}

// DeleteDocPagina elimina una página de documentación.
func DeleteDocPagina(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "ID inválido"})
	}
	if err := models.DeleteDocPagina(uint(id)); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"message": "Página eliminada"})
}

// ─── Rutas públicas / privadas de lectura ─────────────────────────────────────

// DocsPublicoIndex renderiza el índice público de un SaaS (sin login).
func DocsPublicoIndex(c *fiber.Ctx) error {
	saasSlug := c.Params("saas")
	saas, err := models.GetSaasProductoBySlug(saasSlug)
	if err != nil {
		return c.Status(fiber.StatusNotFound).Render("errors/404", fiber.Map{}, "layouts/public")
	}

	paginas, err := models.GetDocPaginasPublicas(saas.ID)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}

	// Agrupar por categoría
	grupos := agruparPorCategoria(paginas)

	return c.Render("docs/public-index", fiber.Map{
		"saas":   saas,
		"grupos": grupos,
	}, "layouts/public")
}

// DocsPublicaPagina renderiza una página pública individual.
func DocsPublicaPagina(c *fiber.Ctx) error {
	saasSlug := c.Params("saas")
	slug := c.Params("slug")

	saas, err := models.GetSaasProductoBySlug(saasSlug)
	if err != nil {
		return c.Status(fiber.StatusNotFound).Render("errors/404", fiber.Map{}, "layouts/public")
	}

	pagina, err := models.GetDocPaginaBySlug(saas.ID, slug)
	if err != nil || !pagina.Publicado {
		return c.Status(fiber.StatusNotFound).Render("errors/404", fiber.Map{}, "layouts/public")
	}
	if pagina.Visibilidad == "private" {
		return c.Status(fiber.StatusForbidden).Render("errors/403", fiber.Map{}, "layouts/public")
	}

	// Índice lateral: páginas públicas del mismo SaaS
	todas, _ := models.GetDocPaginasPublicas(saas.ID)
	grupos := agruparPorCategoria(todas)

	return c.Render("docs/public-page", fiber.Map{
		"saas":   saas,
		"pagina": pagina,
		"grupos": grupos,
	}, "layouts/public")
}

// DocsPrivadoIndex renderiza el índice de un SaaS para usuarios autenticados (incluye privadas con rol).
func DocsPrivadoIndex(c *fiber.Ctx) error {
	saasSlug := c.Params("saas")
	saas, err := models.GetSaasProductoBySlug(saasSlug)
	if err != nil {
		return c.Status(fiber.StatusNotFound).Render("errors/404", fiber.Map{}, "layouts/main")
	}

	rolNames := getUserRolNames(c)
	paginas, err := models.GetDocPaginasParaRoles(saas.ID, rolNames)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	grupos := agruparPorCategoria(paginas)

	return c.Render("docs/private-index", fiber.Map{
		"user":   c.Locals("user"),
		"saas":   saas,
		"grupos": grupos,
	}, "layouts/main")
}

// DocsPrivadaPagina renderiza una página con verificación de rol.
func DocsPrivadaPagina(c *fiber.Ctx) error {
	saasSlug := c.Params("saas")
	slug := c.Params("slug")

	saas, err := models.GetSaasProductoBySlug(saasSlug)
	if err != nil {
		return c.Status(fiber.StatusNotFound).Render("errors/404", fiber.Map{}, "layouts/main")
	}

	pagina, err := models.GetDocPaginaBySlug(saas.ID, slug)
	if err != nil || !pagina.Publicado {
		return c.Status(fiber.StatusNotFound).Render("errors/404", fiber.Map{}, "layouts/main")
	}

	if pagina.Visibilidad == "private" {
		// Verificar que el usuario tenga al menos un rol asignado a la página
		rolNames := getUserRolNames(c)
		if !paginaAccesible(pagina, rolNames) {
			return c.Status(fiber.StatusForbidden).Render("errors/403", fiber.Map{
				"user": c.Locals("user"),
			}, "layouts/main")
		}
	}

	rolNames := getUserRolNames(c)
	todas, _ := models.GetDocPaginasParaRoles(saas.ID, rolNames)
	grupos := agruparPorCategoria(todas)

	return c.Render("docs/private-page", fiber.Map{
		"user":    c.Locals("user"),
		"modules": c.Locals("userModules"),
		"saas":    saas,
		"pagina":  pagina,
		"grupos":  grupos,
	}, "layouts/main")
}

// ─── helpers ──────────────────────────────────────────────────────────────────

type GrupoCategoria struct {
	Categoria models.DocCategoria
	Paginas   []models.DocPagina
}

func agruparPorCategoria(paginas []models.DocPagina) []GrupoCategoria {
	orden := []uint{}
	mapa := map[uint]*GrupoCategoria{}
	for _, p := range paginas {
		if _, ok := mapa[p.CategoriaID]; !ok {
			orden = append(orden, p.CategoriaID)
			mapa[p.CategoriaID] = &GrupoCategoria{Categoria: p.Categoria}
		}
		mapa[p.CategoriaID].Paginas = append(mapa[p.CategoriaID].Paginas, p)
	}
	var result []GrupoCategoria
	for _, id := range orden {
		result = append(result, *mapa[id])
	}
	return result
}

func getUserRolNames(c *fiber.Ctx) []string {
	userMap, ok := c.Locals("user").(map[string]interface{})
	if !ok {
		return nil
	}
	raw, ok := userMap["roles"]
	if !ok {
		return nil
	}
	var names []string
	switch v := raw.(type) {
	case []string:
		names = v
	case []interface{}:
		for _, r := range v {
			if s, ok := r.(string); ok {
				names = append(names, s)
			}
		}
	}
	return names
}

func paginaAccesible(p *models.DocPagina, userRoles []string) bool {
	if p.Visibilidad == "public" {
		return true
	}
	// Sin roles asignados = cualquier usuario autenticado puede ver
	if len(p.Roles) == 0 {
		return true
	}
	for _, pr := range p.Roles {
		for _, ur := range userRoles {
			if pr.Name == ur {
				return true
			}
		}
	}
	return false
}
