package controllers

import (
	"fmt"
	"math"
	"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"
)

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

func LoadFacturas(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.GetAllFacturas(limit, offset, search)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	clientes, _, _ := models.GetAllClientes(200, 0, "")
	proyectos, _, _ := models.GetAllProyectos(200, 0, "")
	return c.JSON(fiber.Map{
		"items":      items,
		"total":      total,
		"totalPages": int(math.Ceil(float64(total) / float64(limit))),
		"page":       page,
		"clientes":   clientes,
		"proyectos":  proyectos,
	})
}

func CreateFactura(c *fiber.Ctx) error {
	type Req struct {
		ClienteID        uint    `json:"cliente_id"`
		ProyectoID       *uint   `json:"proyecto_id"`
		Numero           string  `json:"numero"`
		Descripcion      string  `json:"descripcion"`
		Monto            float64 `json:"monto"`
		Moneda           string  `json:"moneda"`
		Estado           string  `json:"estado"`
		FechaEmision     string  `json:"fecha_emision"`
		FechaVencimiento string  `json:"fecha_vencimiento"`
		Visible          bool    `json:"visible"`
		Notas            string  `json:"notas"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	if req.ClienteID == 0 {
		return c.Status(400).JSON(fiber.Map{"error": "cliente_id es requerido"})
	}
	f := &models.Factura{
		ClienteID:   req.ClienteID,
		ProyectoID:  req.ProyectoID,
		Numero:      req.Numero,
		Descripcion: req.Descripcion,
		Monto:       req.Monto,
		Moneda:      req.Moneda,
		Estado:      req.Estado,
		Visible:     req.Visible,
		Notas:       req.Notas,
		FechaEmision: time.Now(),
	}
	if req.FechaEmision != "" {
		t, err := time.Parse("2006-01-02", req.FechaEmision)
		if err == nil {
			f.FechaEmision = t
		}
	}
	if req.FechaVencimiento != "" {
		t, err := time.Parse("2006-01-02", req.FechaVencimiento)
		if err == nil {
			f.FechaVencimiento = &t
		}
	}
	if f.Moneda == "" {
		f.Moneda = "COP"
	}
	if f.Estado == "" {
		f.Estado = "pendiente"
	}
	if err := models.CreateFactura(f); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.Status(201).JSON(f)
}

func UpdateFactura(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        uint    `json:"cliente_id"`
		ProyectoID       *uint   `json:"proyecto_id"`
		Numero           string  `json:"numero"`
		Descripcion      string  `json:"descripcion"`
		Monto            float64 `json:"monto"`
		Moneda           string  `json:"moneda"`
		Estado           string  `json:"estado"`
		FechaEmision     string  `json:"fecha_emision"`
		FechaVencimiento string  `json:"fecha_vencimiento"`
		Visible          bool    `json:"visible"`
		Notas            string  `json:"notas"`
	}
	var req Req
	if err := c.BodyParser(&req); err != nil {
		return c.Status(400).JSON(fiber.Map{"error": err.Error()})
	}
	f := &models.Factura{
		ClienteID:   req.ClienteID,
		ProyectoID:  req.ProyectoID,
		Numero:      req.Numero,
		Descripcion: req.Descripcion,
		Monto:       req.Monto,
		Moneda:      req.Moneda,
		Estado:      req.Estado,
		Visible:     req.Visible,
		Notas:       req.Notas,
	}
	f.ID = uint(id)
	if req.FechaEmision != "" {
		t, err := time.Parse("2006-01-02", req.FechaEmision)
		if err == nil {
			f.FechaEmision = t
		}
	}
	if req.FechaVencimiento != "" {
		t, err := time.Parse("2006-01-02", req.FechaVencimiento)
		if err == nil {
			f.FechaVencimiento = &t
		}
	}
	if err := models.UpdateFactura(f); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

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

func UploadFacturaPDF(c *fiber.Ctx) error {
	id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
	file, err := c.FormFile("pdf")
	if err != nil {
		return c.Status(400).JSON(fiber.Map{"error": "Archivo PDF requerido"})
	}
	if file.Size > 20*1024*1024 {
		return c.Status(400).JSON(fiber.Map{"error": "Máximo 20MB"})
	}
	dir := "uploads/facturas"
	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, fmt.Sprintf("%d_%s", id, safeFile))
	if err := c.SaveFile(file, savePath); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": "Error al guardar archivo"})
	}
	if err := models.UpdateFacturaArchivo(uint(id), savePath, file.Filename); err != nil {
		return c.Status(500).JSON(fiber.Map{"error": err.Error()})
	}
	// Notificar al cliente
	if f, err := models.GetFacturaByID(uint(id)); err == nil {
		go services.DispatchFacturaSubida(f)
	}
	return c.JSON(fiber.Map{"ok": true, "archivo": savePath})
}

func DownloadFacturaPDF(c *fiber.Ctx) error {
	id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
	f, err := models.GetFacturaByID(uint(id))
	if err != nil || f.Archivo == "" {
		return c.Status(404).JSON(fiber.Map{"error": "Archivo no encontrado"})
	}
	clean := filepath.Clean(f.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"`, f.OriginalName))
	return c.SendFile(clean)
}
