package controllers

import (
	"bytes"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

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

// QueryRunnerPage renderiza la vista del editor SQL.
// GET /app/query-runner?conx_db_id=1
func QueryRunnerPage(c *fiber.Ctx) error {
	conxIDStr := c.Query("conx_db_id", "")
	var conx models.ConxDb
	if conxIDStr != "" {
		id, _ := strconv.ParseUint(conxIDStr, 10, 32)
		app.Http.Database.DB.Preload("TipoDb").Preload("Servidor").First(&conx, id)
	}

	data := fiber.Map{
		"user":       c.Locals("user").(map[string]interface{}),
		"modules":    c.Locals("userModules"),
		"conx_db_id": conxIDStr,
		"conx":       conx,
	}
	if err := c.Render("query_runner", data, "layouts/main"); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": err.Error(),
		})
	}
	return nil
}

// GetConxDbList devuelve todas las conexiones DB disponibles (para el selector).
func GetConxDbList(c *fiber.Ctx) error {
	var items []models.ConxDb
	app.Http.Database.DB.Preload("TipoDb").Preload("Servidor").Find(&items)
	return c.JSON(fiber.Map{"data": items})
}

// GetDatabases lista las bases de datos de una conexión.
// GET /app/query-runner/databases?conx_db_id=1
func GetDatabases(c *fiber.Ctx) error {
	conx, err := loadConxDb(c.Query("conx_db_id", "0"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
	}
	dbs, err := services.ListDatabases(conx)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"data": dbs})
}

// GetTables lista las tablas de una base de datos.
// GET /app/query-runner/tables?conx_db_id=1&db=mydb
func GetTables(c *fiber.Ctx) error {
	conx, err := loadConxDb(c.Query("conx_db_id", "0"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
	}
	dbName := c.Query("db", "")
	tables, err := services.ListTables(conx, dbName)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"data": tables})
}

// RunQuery ejecuta una consulta SQL.
// POST /app/query-runner/run
// Body: { conx_db_id, database, sql }
func RunQuery(c *fiber.Ctx) error {
	var body struct {
		ConxDbID uint   `json:"conx_db_id"`
		Database string `json:"database"`
		SQL      string `json:"sql"`
	}
	if err := c.BodyParser(&body); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cuerpo inválido"})
	}
	if strings.TrimSpace(body.SQL) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "La consulta está vacía"})
	}

	conx, err := loadConxDb(strconv.Itoa(int(body.ConxDbID)))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
	}

	result := services.ExecuteSQL(conx, body.Database, body.SQL)
	return c.JSON(result)
}

// TestConnection verifica que la conexión funciona.
// GET /app/query-runner/test?conx_db_id=1
func TestConnection(c *fiber.Ctx) error {
	conx, err := loadConxDb(c.Query("conx_db_id", "0"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
	}
	if err := services.TestDBConnection(conx); err != nil {
		return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{"ok": false, "error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

// GetHistory devuelve el historial de una conexión.
// GET /app/query-runner/history?conx_db_id=1&page=1
func GetHistory(c *fiber.Ctx) error {
	conxIDStr := c.Query("conx_db_id", "0")
	conxID, _ := strconv.ParseUint(conxIDStr, 10, 32)
	page, _ := strconv.Atoi(c.Query("page", "1"))
	if page < 1 {
		page = 1
	}
	limit := 50
	offset := (page - 1) * limit

	items, total, err := models.GetQueryHistory(uint(conxID), limit, offset)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	totalPages := int(math.Ceil(float64(total) / float64(limit)))
	return c.JSON(fiber.Map{
		"data":       items,
		"total":      total,
		"totalPages": totalPages,
		"page":       page,
	})
}

// ClearHistory borra el historial de una conexión.
// DELETE /app/query-runner/history?conx_db_id=1
func ClearHistory(c *fiber.Ctx) error {
	conxIDStr := c.Query("conx_db_id", "0")
	conxID, _ := strconv.ParseUint(conxIDStr, 10, 32)
	if err := models.DeleteQueryHistory(uint(conxID)); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{"ok": true})
}

// ExportCSV exporta el resultado de una consulta como CSV.
// POST /app/query-runner/export/csv
func ExportCSV(c *fiber.Ctx) error {
	var body struct {
		ConxDbID uint   `json:"conx_db_id"`
		Database string `json:"database"`
		SQL      string `json:"sql"`
	}
	if err := c.BodyParser(&body); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cuerpo inválido"})
	}
	conx, err := loadConxDb(strconv.Itoa(int(body.ConxDbID)))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
	}

	result := services.ExecuteSQL(conx, body.Database, body.SQL)
	if result.Error != "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": result.Error})
	}

	var buf bytes.Buffer
	w := csv.NewWriter(&buf)
	w.Write(result.Columns)
	for _, row := range result.Rows {
		rec := make([]string, len(result.Columns))
		for i, col := range result.Columns {
			v := row[col]
			if v == nil {
				rec[i] = ""
			} else {
				rec[i] = fmt.Sprintf("%v", v)
			}
		}
		w.Write(rec)
	}
	w.Flush()

	filename := fmt.Sprintf("query_%s.csv", time.Now().Format("20060102_150405"))
	c.Set("Content-Disposition", "attachment; filename="+filename)
	c.Set("Content-Type", "text/csv; charset=utf-8")
	return c.SendStream(bytes.NewReader(buf.Bytes()), buf.Len())
}

// ExportJSON exporta el resultado como JSON.
// POST /app/query-runner/export/json
func ExportJSON(c *fiber.Ctx) error {
	var body struct {
		ConxDbID uint   `json:"conx_db_id"`
		Database string `json:"database"`
		SQL      string `json:"sql"`
	}
	if err := c.BodyParser(&body); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cuerpo inválido"})
	}
	conx, err := loadConxDb(strconv.Itoa(int(body.ConxDbID)))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
	}

	result := services.ExecuteSQL(conx, body.Database, body.SQL)
	if result.Error != "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": result.Error})
	}

	data, _ := json.MarshalIndent(result.Rows, "", "  ")
	filename := fmt.Sprintf("query_%s.json", time.Now().Format("20060102_150405"))
	c.Set("Content-Disposition", "attachment; filename="+filename)
	c.Set("Content-Type", "application/json; charset=utf-8")
	return c.SendStream(bytes.NewReader(data), len(data))
}

// ── helper ───────────────────────────────────────────────────────────────────

func loadConxDb(idStr string) (models.ConxDb, error) {
	id, err := strconv.ParseUint(idStr, 10, 32)
	if err != nil || id == 0 {
		return models.ConxDb{}, fmt.Errorf("conx_db_id inválido")
	}
	var conx models.ConxDb
	if err := app.Http.Database.DB.Preload("TipoDb").Preload("Servidor").First(&conx, id).Error; err != nil {
		return models.ConxDb{}, fmt.Errorf("conexión no encontrada: %w", err)
	}
	return conx, nil
}
