package controllers

import (
	"crypto/rand"
	"errors"
	"fmt"
	"log"
	"math/big"
	"strings"

	"github.com/gofiber/fiber/v2"
	"github.com/sujit-baniya/fiber-boilerplate/app"
	"github.com/sujit-baniya/fiber-boilerplate/config"
	"github.com/sujit-baniya/fiber-boilerplate/pkg/auth"
	"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
	"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"
)

// Función para renderizar la página de registro
func RegisterGet(c *fiber.Ctx) error {
	data := getFlashData(c)
	data["title"] = "Register | "
	if err := c.Render("auth/register", data, "layouts/landing"); err != nil { //nolint:wsl
		panic(err.Error())
	}
	return nil
}

// Función para manejar la solicitud de registro
func RegisterPost(c *fiber.Ctx) error {
	register := c.Locals("register").(models.RegisterForm)
	user, err := register.Signup()
	if err != nil {
		return app.Http.Flash.WithError(c, fiber.Map{
			"error":   true,
			"message": "Error on register request: " + err.Error(),
		}).Redirect("/register")
	}
	store := app.Http.Session.Get(c) // obtener/crear nueva sesión
	store.Set("user_id", user.ID)    // guardar en el almacenamiento
	_ = store.Save()

	go services.SendConfirmationEmail(user.Email, services.GetPublicURL(), user.NombreUsuario)
	return c.Redirect("/")
}

// Función para verificar el correo electrónico registrado
func VerifyRegisteredEmail(c *fiber.Ctx) error {

	return c.Redirect("/app")
}

// Función para reenviar el correo de confirmación
func ResendConfirmEmail(c *fiber.Ctx) error {
	user, _ := auth.User(c)
	go services.SendConfirmationEmail(user.Email, services.GetPublicURL(), user.NombreUsuario)
	return c.Redirect("/")
}

func ReenvioEmail(c *fiber.Ctx) error {
	usuarioID := c.Params("id") // Usa Params para obtener el parámetro de la ruta
	user, err := models.GetUserById(usuarioID)
	if err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"success": false,
			"message": "Usuario no encontrado: " + err.Error(),
		})
	}

	// Reenvío del correo electrónico de forma asíncrona
	go services.SendConfirmationEmail(user.Email, services.GetPublicURL(), user.NombreUsuario)

	return c.Status(fiber.StatusOK).JSON(fiber.Map{
		"success": true,
		"message": "Email reenviado correctamente",
	})
}

// Función para manejar la solicitud de restablecimiento de contraseña
func RequestPasswordResetPost(c *fiber.Ctx) error {
	email := strings.TrimSpace(c.FormValue("email"))
	if email == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"success": false,
			"message": "El correo electrónico es requerido.",
		})
	}

	// Intentar enviar el correo solo si el usuario existe — mismo mensaje en ambos casos por seguridad
	user, err := models.GetUserByEmail(email)
	if err == nil && user != nil && user.Email != "" {
		log.Println("[Reset] Enviando enlace de recuperación a:", email)
		go services.SendPasswordResetEmail(user.Email, services.GetPublicURL())
	} else {
		log.Println("[Reset] Correo no encontrado (no se revela al cliente):", email)
	}

	return c.JSON(fiber.Map{
		"success": true,
		"message": "Si el correo está registrado, recibirás instrucciones en los próximos minutos.",
	})
}


// todo: Generar contrasenas aleatorias /do/generate-password
func generatePassword(length int) (string, error) {
	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&"
	var password strings.Builder
	for i := 0; i < length; i++ {
		randomIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
		if err != nil {
			return "", err
		}
		password.WriteByte(charset[randomIndex.Int64()])
	}
	return password.String(), nil
}

func hashPassword(password string) (string, error) {
	hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return "", err
	}
	return string(hashed), nil
}

func updatePassword(nombreUsuario, plainPassword string) error {
	var user models.RegisterForm

	result := app.Http.Database.DB.Where("nombre_usuario = ?", nombreUsuario).First(&user)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		return errors.New("codigo no pertenece a ningun usuario")
	}

	if user.Password != "" {
		return errors.New("este usuario ya cuenta con una contraseña")
	}

	hashedPassword, err := hashPassword(plainPassword)
	if err != nil {
		return err
	}

	user.Password = hashedPassword
	if err := app.Http.Database.DB.Save(&user).Error; err != nil {
		return err
	}

	return nil
}

func GeneratePasswordPost(c *fiber.Ctx) error {
	// Parsear el cuerpo de la solicitud
	var body struct {
		NombreUsuario string `json:"nombre_usuario"`
	}
	if err := c.BodyParser(&body); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"success": false,
			"message": "Error al analizar el cuerpo de la solicitud",
		})
	}

	if body.NombreUsuario == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"success": false,
			"message": "El codigo de usuario es requerido",
		})
	}

	// Generar la contraseña
	password, err := generatePassword(12)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"success": false,
			"message": "Error generando contraseña",
		})
	}

	// Actualizar la contraseña en la base de datos
	if err := updatePassword(body.NombreUsuario, password); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"success": false,
			"message": err.Error(),
		})
	}

	// Responder con la contraseña generada
	return c.JSON(fiber.Map{
		"success":  true,
		"message":  "Contrasena generada exitosamente",
		"password": password,
	})
}

// Función para renderizar la página de restablecimiento de contraseña
func PasswordReset(c *fiber.Ctx) error {
	token := c.Query("t")
	if err := c.Render("auth/password-reset", fiber.Map{
		"Title": "Password Reset",
		"Token": token,
	}, "layouts/landing"); err != nil {
		panic(err.Error())
	}
	return nil
}

func PasswordResetPost(c *fiber.Ctx) error {
	// El email fue validado y extraído del token por el middleware ValidatePasswordResetPost
	emailLocal := c.Locals("email")
	if emailLocal == nil {
		return c.Status(fiber.StatusBadRequest).Render("auth/password-reset", fiber.Map{
			"Token": c.Params("token"),
			"error": "Token inválido. Solicita un nuevo enlace.",
		}, "layouts/landing")
	}
	email := fmt.Sprintf("%v", emailLocal)

	password := c.FormValue("password")
	cPassword := c.FormValue("c_password")

	if password == "" || len(password) < 6 {
		return c.Render("auth/password-reset", fiber.Map{
			"Token": c.Params("token"),
			"error": "La contraseña debe tener al menos 6 caracteres.",
		}, "layouts/landing")
	}
	if password != cPassword {
		return c.Render("auth/password-reset", fiber.Map{
			"Token": c.Params("token"),
			"error": "Las contraseñas no coinciden.",
		}, "layouts/landing")
	}

	user, err := models.GetUserByEmail(email)
	if err != nil || user == nil {
		return c.Render("auth/password-reset", fiber.Map{
			"Token": c.Params("token"),
			"error": "No se encontró una cuenta asociada a este enlace.",
		}, "layouts/landing")
	}

	hash := config.Hash{}
	hashedPassword, err := hash.Create(password)
	if err != nil {
		return c.Render("auth/password-reset", fiber.Map{
			"Token": c.Params("token"),
			"error": "Error al procesar la contraseña. Intenta de nuevo.",
		}, "layouts/landing")
	}

	if err := models.UpdateUserPassword(user.ID, hashedPassword); err != nil {
		return c.Render("auth/password-reset", fiber.Map{
			"Token": c.Params("token"),
			"error": "No se pudo actualizar la contraseña. Intenta de nuevo.",
		}, "layouts/landing")
	}

	return c.Redirect("/login")
}

// Función para renderizar la página de solicitud de restablecimiento de contraseña
func RequestPasswordReset(c *fiber.Ctx) error {
	data := getFlashData(c)
	data["title"] = "Password Reset | "
	if err := c.Render("auth/request-password-reset", data, "layouts/landing"); err != nil { //nolint:wsl
		panic(err.Error())
	}
	return nil
}

func TokenVencido(c *fiber.Ctx) error {
	// Renderiza la vista con el layout layouts/main
	if err := c.Render("token_vencido", nil, "layouts/landing"); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"message": "Error rendering template",
			"error":   err.Error(),
		})
	}
	return nil
}
