package controllers

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
	"unicode"

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

func TareasIndex(c *fiber.Ctx) error {
	return c.Render("tareas", fiber.Map{
		"user":    c.Locals("user"),
		"modules": c.Locals("userModules"),
	}, "layouts/main")
}

func GetTareas(c *fiber.Ctx) error {
	items, err := models.GetAllTareas()
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(items)
}

func GetUsuariosSistema(c *fiber.Ctx) error {
	users, _, err := models.AllUsersSistema(100, 0, "")
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(users)
}

func CreateTarea(c *fiber.Ctx) error {
	var req struct {
		Titulo      string  `json:"titulo"`
		Descripcion string  `json:"descripcion"`
		Prioridad   string  `json:"prioridad"`
		AsignadoID  *uint   `json:"asignado_id"`
		FechaLimite *string `json:"fecha_limite"`
	}
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	if strings.TrimSpace(req.Titulo) == "" {
		return c.Status(400).JSON(fiber.Map{"error": "titulo requerido"})
	}

	autorID := extraerUserID(c)

	tarea := &models.Tarea{
		Titulo:      req.Titulo,
		Descripcion: req.Descripcion,
		Estado:      "por_hacer",
		Prioridad:   prioPorDefecto(req.Prioridad),
		AsignadoID:  req.AsignadoID,
		CreadoPorID: autorID,
	}
	if req.FechaLimite != nil && *req.FechaLimite != "" {
		if t, err := time.Parse("2006-01-02", *req.FechaLimite); err == nil {
			tarea.FechaLimite = &t
		}
	}
	if err := models.CreateTarea(tarea); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	// reload con preloads
	t, _ := models.GetTareaByID(tarea.ID)
	go services.NotificarTareaAsignada(t)
	return c.Status(201).JSON(t)
}

func UpdateTarea(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
	}
	tarea, err := models.GetTareaByID(uint(id))
	if err != nil {
		return c.Status(404).JSON(fiber.Map{"error": "no encontrado"})
	}

	var req struct {
		Titulo      string  `json:"titulo"`
		Descripcion string  `json:"descripcion"`
		Prioridad   string  `json:"prioridad"`
		AsignadoID  *uint   `json:"asignado_id"`
		FechaLimite *string `json:"fecha_limite"`
	}
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}

	prevAsignado := tarea.AsignadoID
	tarea.Titulo = req.Titulo
	tarea.Descripcion = req.Descripcion
	tarea.Prioridad = prioPorDefecto(req.Prioridad)
	tarea.AsignadoID = req.AsignadoID
	tarea.FechaLimite = nil
	if req.FechaLimite != nil && *req.FechaLimite != "" {
		if t, err := time.Parse("2006-01-02", *req.FechaLimite); err == nil {
			tarea.FechaLimite = &t
		}
	}
	if err := models.SaveTarea(tarea); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	t, _ := models.GetTareaByID(tarea.ID)
	// notificar si cambió el asignado
	if cambioPuntero(prevAsignado, req.AsignadoID) {
		go services.NotificarTareaAsignada(t)
	}
	return c.JSON(t)
}

func CambiarEstadoTarea(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
	}
	var req struct {
		Estado string `json:"estado"`
	}
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	estados := map[string]bool{"por_hacer": true, "en_progreso": true, "revision": true, "hecho": true}
	if !estados[req.Estado] {
		return c.Status(400).JSON(fiber.Map{"error": "estado inválido"})
	}
	if err := models.CambiarEstadoTarea(uint(id), req.Estado); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	t, _ := models.GetTareaByID(uint(id))
	go services.NotificarTareaEstado(t)
	return c.JSON(fiber.Map{"ok": true})
}

func DeleteTareaHandler(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
	}
	if err := models.DeleteTarea(uint(id)); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

func GetTareaDetalle(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
	}
	tarea, err := models.GetTareaByID(uint(id))
	if err != nil {
		return c.Status(404).JSON(fiber.Map{"error": "no encontrado"})
	}
	comentarios, _ := models.GetComentariosByTarea(uint(id))
	return c.JSON(fiber.Map{"tarea": tarea, "comentarios": comentarios})
}

func AddComentario(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
	}
	tarea, err := models.GetTareaByID(uint(id))
	if err != nil {
		return c.Status(404).JSON(fiber.Map{"error": "tarea no encontrada"})
	}

	contenido := strings.TrimSpace(c.FormValue("contenido"))
	if contenido == "" {
		return c.Status(400).JSON(fiber.Map{"error": "contenido requerido"})
	}

	autorID := extraerUserID(c)
	archivosJSON := "[]"

	// archivo adjunto opcional
	file, fileErr := c.FormFile("archivo")
	if fileErr == nil && file != nil {
		if file.Size > 50*1024*1024 {
			return c.Status(400).JSON(fiber.Map{"error": "Máximo 50MB por archivo"})
		}
		dir := fmt.Sprintf("uploads/tareas/%d", id)
		if err := os.MkdirAll(dir, 0755); err != nil {
			return c.Status(500).JSON(fiber.Map{"error": "Error creando directorio"})
		}
		safeFile := safeTareaFilename(file.Filename)
		savePath := filepath.Join(dir, fmt.Sprintf("%d_%s", time.Now().UnixMilli(), safeFile))
		clean := filepath.Clean(savePath)
		if !strings.HasPrefix(clean, "uploads/") {
			return c.Status(400).JSON(fiber.Map{"error": "Ruta inválida"})
		}
		if err := c.SaveFile(file, savePath); err != nil {
			return c.Status(500).JSON(fiber.Map{"error": "Error guardando archivo"})
		}
		archivosJSON = fmt.Sprintf(`[{"nombre":"%s","ruta":"%s"}]`,
			escapeJSON(file.Filename), escapeJSON(savePath))
	}

	comentario := &models.TareaComentario{
		TareaID:   uint(id),
		AutorID:   autorID,
		Contenido: contenido,
		Archivos:  archivosJSON,
	}
	if err := models.CreateComentario(comentario); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	go services.NotificarTareaComentario(tarea, contenido)
	return c.Status(201).JSON(comentario)
}

func extraerUserID(c *fiber.Ctx) uint {
	userMap, _ := c.Locals("user").(map[string]interface{})
	if userMap == nil {
		return 0
	}
	switch v := userMap["id"].(type) {
	case float64:
		return uint(v)
	case uint:
		return v
	}
	return 0
}

func prioPorDefecto(p string) string {
	valid := map[string]bool{"baja": true, "media": true, "alta": true, "urgente": true}
	if valid[p] {
		return p
	}
	return "media"
}

func cambioPuntero(a, b *uint) bool {
	if a == nil && b == nil {
		return false
	}
	if a == nil || b == nil {
		return true
	}
	return *a != *b
}

func safeTareaFilename(name string) string {
	base := filepath.Base(name)
	var out []rune
	for _, r := range base {
		if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '.' || r == '-' || r == '_' {
			out = append(out, r)
		} else {
			out = append(out, '_')
		}
	}
	if len(out) == 0 {
		return "archivo"
	}
	return string(out)
}

func escapeJSON(s string) string {
	s = strings.ReplaceAll(s, `\`, `\\`)
	s = strings.ReplaceAll(s, `"`, `\"`)
	return s
}
