package controllers

import (
	"fmt"
	"math"
	"mime/multipart"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

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

// ─── Admin pages ──────────────────────────────────────────────────────────────

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

func ProyectoDetalleIndex(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 32)
	if err != nil {
		return c.Redirect("/app/proyectos")
	}
	proy, err := models.GetProyectoByID(uint(id))
	if err != nil {
		return c.Redirect("/app/proyectos")
	}
	return c.Render("proyecto_detalle", fiber.Map{
		"user":     c.Locals("user"),
		"modules":  c.Locals("userModules"),
		"proyecto": proy,
	}, "layouts/main")
}

// ─── CRUD Proyecto ─────────────────────────────────────────────────────────────

func LoadProyectos(c *fiber.Ctx) error {
	page, _ := strconv.Atoi(c.Query("page", "1"))
	search := c.Query("search", "")
	if page < 1 {
		page = 1
	}
	limit := 20
	offset := (page - 1) * limit
	items, total, err := models.GetAllProyectos(limit, offset, search)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	// Obtener clientes para el formulario
	clientes, _, _ := models.GetAllClientes(200, 0, "")
	return c.JSON(fiber.Map{
		"items":      items,
		"total":      total,
		"totalPages": int(math.Ceil(float64(total) / float64(limit))),
		"page":       page,
		"clientes":   clientes,
	})
}

func CreateProyecto(c *fiber.Ctx) error {
	type Req struct {
		ClienteID   interface{} `json:"cliente_id"`
		Nombre      string      `json:"nombre"`
		Slug        string      `json:"slug"`
		Descripcion string      `json:"descripcion"`
		Color       string      `json:"color"`
		Estado      string      `json:"estado"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	clienteID := parseUintField(req.ClienteID)
	if clienteID == 0 || strings.TrimSpace(req.Nombre) == "" {
		return c.Status(400).JSON(fiber.Map{"error": "cliente y nombre son requeridos"})
	}
	if req.Slug == "" {
		req.Slug = slugify(req.Nombre)
	}
	if req.Color == "" {
		req.Color = "#8eb02f"
	}
	if req.Estado == "" {
		req.Estado = "activo"
	}
	p := &models.Proyecto{
		ClienteID:   clienteID,
		Nombre:      req.Nombre,
		Slug:        req.Slug,
		Descripcion: req.Descripcion,
		Color:       req.Color,
		Estado:      req.Estado,
	}
	if err := models.CreateProyecto(p); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.Status(201).JSON(p)
}

func UpdateProyecto(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"})
	}
	type Req struct {
		ClienteID   interface{} `json:"cliente_id"`
		Nombre      string      `json:"nombre"`
		Slug        string      `json:"slug"`
		Descripcion string      `json:"descripcion"`
		Color       string      `json:"color"`
		Estado      string      `json:"estado"`
		Progreso    int         `json:"progreso"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	clienteID := parseUintField(req.ClienteID)
	p := &models.Proyecto{
		ClienteID:   clienteID,
		Nombre:      req.Nombre,
		Slug:        req.Slug,
		Descripcion: req.Descripcion,
		Color:       req.Color,
		Estado:      req.Estado,
		Progreso:    req.Progreso,
	}
	p.ID = uint(id)
	if err := models.UpdateProyecto(p); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

func DeleteProyecto(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.DeleteProyecto(uint(id)); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

// ─── Fases ────────────────────────────────────────────────────────────────────

func GetFases(c *fiber.Ctx) error {
	id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
	fases, err := models.GetFasesByProyecto(uint(id))
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fases)
}

func CreateFase(c *fiber.Ctx) error {
	proyID, _ := strconv.ParseUint(c.Params("id"), 10, 32)
	type Req struct {
		Nombre        string   `json:"nombre"`
		Descripcion   string   `json:"descripcion"`
		Estado        string   `json:"estado"`
		Orden         int      `json:"orden"`
		FechaEstimada string   `json:"fecha_estimada"`
		Entregables   []string `json:"entregables"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	f := &models.ProyectoFase{
		ProyectoID:  uint(proyID),
		Nombre:      req.Nombre,
		Descripcion: req.Descripcion,
		Estado:      req.Estado,
		Orden:       req.Orden,
		Entregables: req.Entregables,
	}
	if req.FechaEstimada != "" {
		t, err := time.Parse("2006-01-02", req.FechaEstimada)
		if err == nil {
			f.FechaEstimada = &t
		}
	}
	if err := models.CreateProyectoFase(f); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	f.ParseEntregables()
	_ = models.ActualizarProgresoProyecto(uint(proyID))
	return c.Status(201).JSON(f)
}

func UpdateFase(c *fiber.Ctx) error {
	faseID, _ := strconv.ParseUint(c.Params("faseID"), 10, 32)
	proyID, _ := strconv.ParseUint(c.Params("id"), 10, 32)
	type Req struct {
		Nombre          string   `json:"nombre"`
		Descripcion     string   `json:"descripcion"`
		Estado          string   `json:"estado"`
		Orden           int      `json:"orden"`
		FechaEstimada   string   `json:"fecha_estimada"`
		FechaCompletado string   `json:"fecha_completado"`
		Entregables     []string `json:"entregables"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	f := &models.ProyectoFase{
		Nombre:      req.Nombre,
		Descripcion: req.Descripcion,
		Estado:      req.Estado,
		Orden:       req.Orden,
		Entregables: req.Entregables,
	}
	f.ID = uint(faseID)
	if req.FechaEstimada != "" {
		t, err := time.Parse("2006-01-02", req.FechaEstimada)
		if err == nil {
			f.FechaEstimada = &t
		}
	}
	if req.FechaCompletado != "" {
		t, err := time.Parse("2006-01-02", req.FechaCompletado)
		if err == nil {
			f.FechaCompletado = &t
		}
	}
	// Auto-set fecha_completado when estado = completado
	if req.Estado == "completado" && f.FechaCompletado == nil {
		now := time.Now()
		f.FechaCompletado = &now
	}
	if err := models.UpdateProyectoFase(f); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	_ = models.ActualizarProgresoProyecto(uint(proyID))
	return c.JSON(fiber.Map{"ok": true})
}

func DeleteFase(c *fiber.Ctx) error {
	faseID, _ := strconv.ParseUint(c.Params("faseID"), 10, 32)
	proyID, _ := strconv.ParseUint(c.Params("id"), 10, 32)
	if err := models.DeleteProyectoFase(uint(faseID)); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	_ = models.ActualizarProgresoProyecto(uint(proyID))
	return c.JSON(fiber.Map{"ok": true})
}

func ApplyTemplate(c *fiber.Ctx) error {
	id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
	if err := models.ApplyFaseTemplate(uint(id)); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	_ = models.ActualizarProgresoProyecto(uint(id))
	fases, _ := models.GetFasesByProyecto(uint(id))
	return c.JSON(fases)
}

// ─── Avances ──────────────────────────────────────────────────────────────────

func GetAvances(c *fiber.Ctx) error {
	id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
	avances, err := models.GetAvancesByProyecto(uint(id), false)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(avances)
}

func CreateAvance(c *fiber.Ctx) error {
	proyID, _ := strconv.ParseUint(c.Params("id"), 10, 32)
	var a models.ProyectoAvance
	if err := c.BodyParser(&a); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	a.ProyectoID = uint(proyID)
	if a.Tipo == "" {
		a.Tipo = "update"
	}
	a.Visible = true
	if err := models.CreateProyectoAvance(&a); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.Status(201).JSON(a)
}

func UpdateAvance(c *fiber.Ctx) error {
	avID, _ := strconv.ParseUint(c.Params("avID"), 10, 32)
	var a models.ProyectoAvance
	if err := c.BodyParser(&a); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	a.ID = uint(avID)
	if err := models.UpdateProyectoAvance(&a); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

func DeleteAvance(c *fiber.Ctx) error {
	avID, _ := strconv.ParseUint(c.Params("avID"), 10, 32)
	if err := models.DeleteProyectoAvance(uint(avID)); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

// ─── Entregables ──────────────────────────────────────────────────────────────

func GetEntregables(c *fiber.Ctx) error {
	id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
	items, err := models.GetEntregablesByProyecto(uint(id), false)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(items)
}

func UploadEntregable(c *fiber.Ctx) error {
	proyID, _ := strconv.ParseUint(c.Params("id"), 10, 32)
	if _, err := c.MultipartForm(); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "Formato multipart requerido"})
	}
	nombre := c.FormValue("nombre")
	descripcion := c.FormValue("descripcion")
	version := c.FormValue("version", "1.0")

	file, err := c.FormFile("archivo")
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "Archivo requerido"})
	}
	if file.Size > 50*1024*1024 {
		return c.Status(400).JSON(fiber.Map{"error": "Archivo máximo 50MB"})
	}

	dir := fmt.Sprintf("uploads/proyectos/%d", proyID)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": "Error al crear directorio"})
	}

	safeFile := safeFilenameProyecto(file.Filename)
	savePath := filepath.Join(dir, safeFile)
	if err := c.SaveFile(file, savePath); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": "Error al guardar archivo"})
	}

	e := &models.ProyectoEntregable{
		ProyectoID:   uint(proyID),
		Nombre:       nombre,
		Descripcion:  descripcion,
		Archivo:      savePath,
		OriginalName: file.Filename,
		Version:      version,
		TipoMime:     mimeFromHeader(file),
		Tamanio:      file.Size,
		Visible:      true,
	}
	if err := models.CreateProyectoEntregable(e); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.Status(201).JSON(e)
}

func DeleteEntregable(c *fiber.Ctx) error {
	entID, _ := strconv.ParseUint(c.Params("entID"), 10, 32)
	item, err := models.GetProyectoEntregableByID(uint(entID))
	if err != nil {
		return c.Status(404).JSON(fiber.Map{"error": "No encontrado"})
	}
	_ = os.Remove(item.Archivo)
	if err := models.DeleteProyectoEntregable(uint(entID)); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

func DownloadEntregable(c *fiber.Ctx) error {
	entID, _ := strconv.ParseUint(c.Params("entID"), 10, 32)
	item, err := models.GetProyectoEntregableByID(uint(entID))
	if err != nil {
		return c.Status(404).JSON(fiber.Map{"error": "No encontrado"})
	}
	clean := filepath.Clean(item.Archivo)
	if !strings.HasPrefix(clean, "uploads/") {
		return c.Status(403).JSON(fiber.Map{"error": "Acceso denegado"})
	}
	c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, item.OriginalName))
	return c.SendFile(clean)
}

func UpdateEntregableVisibilidad(c *fiber.Ctx) error {
	entID, _ := strconv.ParseUint(c.Params("entID"), 10, 32)
	type Req struct{ Visible bool `json:"visible"` }
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	if err := models.UpdateEntregableVisibilidad(uint(entID), req.Visible); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

// ─── Documentos (contratos / órdenes de servicio) ─────────────────────────────

func GetDocumentos(c *fiber.Ctx) error {
	id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
	items, err := models.GetDocumentosByProyecto(uint(id))
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(items)
}

func UploadDocumento(c *fiber.Ctx) error {
	proyID, _ := strconv.ParseUint(c.Params("id"), 10, 32)
	if _, err := c.MultipartForm(); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "Formato multipart requerido"})
	}
	nombre := strings.TrimSpace(c.FormValue("nombre"))
	descripcion := c.FormValue("descripcion")
	tipo := c.FormValue("tipo", "contrato")

	validTipos := map[string]bool{"contrato": true, "orden_servicio": true, "otro": true}
	if !validTipos[tipo] {
		tipo = "otro"
	}

	file, err := c.FormFile("archivo")
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "Archivo requerido"})
	}
	if file.Size > 50*1024*1024 {
		return c.Status(400).JSON(fiber.Map{"error": "Archivo máximo 50MB"})
	}
	if nombre == "" {
		nombre = file.Filename
	}

	dir := fmt.Sprintf("uploads/proyectos/%d/docs", proyID)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": "Error al crear directorio"})
	}

	safeFile := safeFilenameProyecto(file.Filename)
	savePath := filepath.Join(dir, safeFile)
	if err := c.SaveFile(file, savePath); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": "Error al guardar archivo"})
	}

	d := &models.ProyectoDocumento{
		ProyectoID:   uint(proyID),
		Tipo:         tipo,
		Nombre:       nombre,
		Descripcion:  descripcion,
		Archivo:      savePath,
		OriginalName: file.Filename,
		TipoMime:     mimeFromHeader(file),
		Tamanio:      file.Size,
	}
	if err := models.CreateProyectoDocumento(d); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.Status(201).JSON(d)
}

func DeleteDocumento(c *fiber.Ctx) error {
	docID, _ := strconv.ParseUint(c.Params("docID"), 10, 32)
	item, err := models.GetProyectoDocumentoByID(uint(docID))
	if err != nil {
		return c.Status(404).JSON(fiber.Map{"error": "No encontrado"})
	}
	_ = os.Remove(item.Archivo)
	if err := models.DeleteProyectoDocumento(uint(docID)); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

func DownloadDocumento(c *fiber.Ctx) error {
	docID, _ := strconv.ParseUint(c.Params("docID"), 10, 32)
	item, err := models.GetProyectoDocumentoByID(uint(docID))
	if err != nil {
		return c.Status(404).JSON(fiber.Map{"error": "No encontrado"})
	}
	clean := filepath.Clean(item.Archivo)
	if !strings.HasPrefix(clean, "uploads/") {
		return c.Status(403).JSON(fiber.Map{"error": "Acceso denegado"})
	}
	nombre := item.OriginalName
	if nombre == "" {
		nombre = item.Nombre
	}
	c.Set("Content-Disposition", attachmentDisposition(nombre))
	return c.SendFile(clean)
}

// attachmentDisposition genera un header Content-Disposition con fallback ASCII y encoding RFC 5987.
func attachmentDisposition(name string) string {
	safe := strings.Map(func(r rune) rune {
		if r > 127 || r == '"' || r == '\\' || r == '/' || r == '\n' || r == '\r' {
			return '_'
		}
		return r
	}, name)
	return fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, safe, url.PathEscape(name))
}

// ─── Tickets (admin) ──────────────────────────────────────────────────────────

func GetTickets(c *fiber.Ctx) error {
	id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
	items, err := models.GetTicketsByProyecto(uint(id))
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(items)
}

func UpdateTicketEstadoAdmin(c *fiber.Ctx) error {
	ticketID, _ := strconv.ParseUint(c.Params("ticketID"), 10, 32)
	type Req struct{ Estado string `json:"estado"` }
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	if err := models.UpdateTicketEstado(uint(ticketID), req.Estado); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

func AdminResponderTicket(c *fiber.Ctx) error {
	ticketID, _ := strconv.ParseUint(c.Params("ticketID"), 10, 32)
	type Req struct{ Contenido string `json:"contenido"` }
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	if strings.TrimSpace(req.Contenido) == "" {
		return c.Status(400).JSON(fiber.Map{"error": "El mensaje no puede estar vacío"})
	}
	// Obtener nombre del admin
	adminNombre := "Soporte"
	if u := c.Locals("user"); u != nil {
		if m, ok := u.(map[string]interface{}); ok {
			if n, ok := m["Name"].(string); ok && n != "" {
				adminNombre = n
			}
		}
	}
	msg := &models.TicketMensaje{
		TicketID:    uint(ticketID),
		Contenido:   req.Contenido,
		EsAdmin:     true,
		AutorNombre: adminNombre,
	}
	if err := models.CreateTicketMensaje(msg); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	// Cambiar estado a en_progreso si estaba abierto
	_ = models.UpdateTicketEstado(uint(ticketID), "en_progreso")
	// Notificar al cliente
	go func() {
		ticket, err := models.GetTicketByID(uint(ticketID))
		if err != nil {
			return
		}
		proyNombre := ""
		if ticket.ProyectoID != nil {
			if proy, err := models.GetProyectoByID(*ticket.ProyectoID); err == nil {
				proyNombre = proy.Nombre
			}
		}
		services.DispatchTicketRespuestaAdmin(ticket, req.Contenido, proyNombre)
	}()
	return c.Status(201).JSON(msg)
}

// ─── Tickets global (admin) ──────────────────────────────────────────────────

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

func GetAllTicketsAdmin(c *fiber.Ctx) error {
	estado := c.Query("estado", "todos")
	items, err := models.GetAllTickets(estado)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(items)
}

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

func slugify(s string) string {
	s = strings.ToLower(s)
	replacer := strings.NewReplacer(
		" ", "-", "á", "a", "é", "e", "í", "i", "ó", "o", "ú", "u",
		"ñ", "n", "ü", "u",
	)
	s = replacer.Replace(s)
	var b strings.Builder
	for _, r := range s {
		if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
			b.WriteRune(r)
		}
	}
	return strings.Trim(b.String(), "-")
}

// parseUintField convierte interface{} (string o float64 del JSON) a uint de forma segura.
func parseUintField(v interface{}) uint {
	switch val := v.(type) {
	case float64:
		return uint(val)
	case string:
		n, _ := strconv.ParseUint(strings.TrimSpace(val), 10, 64)
		return uint(n)
	}
	return 0
}

func safeFilenameProyecto(name string) string {
	base := filepath.Base(name)
	ext := filepath.Ext(base)
	stem := strings.TrimSuffix(base, ext)
	safe := strings.Map(func(r rune) rune {
		if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '_' {
			return r
		}
		return '_'
	}, stem)
	ts := strconv.FormatInt(time.Now().UnixNano(), 36)
	return safe + "_" + ts + ext
}

func mimeFromHeader(fh *multipart.FileHeader) string {
	ct := fh.Header.Get("Content-Type")
	if ct == "" {
		return "application/octet-stream"
	}
	return ct
}
