package controllers

import (
	"errors"
	"strconv"
	"time"

	"github.com/gofiber/fiber/v3"
	"github.com/google/uuid"
	"gorm.io/gorm"

	"server/internal/auth"
	"server/internal/models"
)

type UserController struct{}

func NewUserController() *UserController {
	return &UserController{}
}

// Typescript: interface
type UpdateUserRequest struct {
	Name        string                       `json:"name" validate:"required,min=1,max=255"`
	Email       string                       `json:"email" validate:"required,email"`
	Password    string                       `json:"password" validate:"omitempty,min=8,max=128"`
	Roles       models.UserRoles             `json:"roles"`
	Status      models.UserStatus            `json:"status"`
	Types       models.UserTypes             `json:"types"`
	Avatar      *string                      `json:"avatar"`
	Details     *models.UserDetailsShort     `json:"details"`
	Preferences *models.UserPreferencesShort `json:"preferences"`
}

// GetUser returns a single user by UUID.
func (uc *UserController) GetUser(c fiber.Ctx) error {
	user, err := loadUserByUUID(c)
	if err != nil {
		return err
	}
	return c.JSON(success(models.ToUserProfile(user)))
}

// CreateUser creates a user together with optional details and preferences.
func (uc *UserController) CreateUser(c fiber.Ctx) error {
	var req models.UserCreateInput
	if err := c.Bind().Body(&req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
	}
	if err := validateStruct(&req); err != nil {
		return err
	}

	db, err := dbFromCtx(c)
	if err != nil {
		return err
	}

	var existing models.User
	if err := db.Where("email = ?", req.Email).First(&existing).Error; err == nil {
		return fiber.NewError(fiber.StatusConflict, "user already exists")
	} else if !errors.Is(err, gorm.ErrRecordNotFound) {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to check user")
	}

	hashedPassword, err := auth.HashPassword(req.Password)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to secure password")
	}

	now := time.Now().UTC()
	user := models.User{
		Email:    req.Email,
		Name:     req.Name,
		Password: hashedPassword,
		Roles: func() models.UserRoles {
			if len(req.Roles) == 0 {
				return models.UserRoles{"user"}
			}
			return req.Roles
		}(),
		Status: func() models.UserStatus {
			if req.Status == "" {
				return models.UserStatusPending
			}
			return req.Status
		}(),
		Types: func() models.UserTypes {
			if len(req.Types) == 0 {
				return models.UserTypes{"internal"}
			}
			return req.Types
		}(),
		Avatar:      req.Avatar,
		UUID:        uuid.NewString(),
		Details:     toUserDetails(req.Details),
		Preferences: toUserPreferences(req.Preferences),
		CreatedAt:   now,
		UpdatedAt:   now,
	}

	if err := db.Create(&user).Error; err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to create user")
	}

	if err := db.Preload("Details").Preload("Preferences").First(&user, user.ID).Error; err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to reload user")
	}

	return c.Status(fiber.StatusCreated).JSON(success(models.ToUserProfile(&user)))
}

// UpdateUser replaces user fields and synchronizes details/preferences.
func (uc *UserController) UpdateUser(c fiber.Ctx) error {
	var req UpdateUserRequest
	if err := c.Bind().Body(&req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
	}
	if err := validateStruct(&req); err != nil {
		return err
	}

	db, err := dbFromCtx(c)
	if err != nil {
		return err
	}

	user, err := loadUserByUUID(c)
	if err != nil {
		return err
	}

	if req.Email != user.Email {
		var existing models.User
		if err := db.Select("id").Where("email = ?", req.Email).First(&existing).Error; err == nil && existing.ID != user.ID {
			return fiber.NewError(fiber.StatusConflict, "user already exists")
		} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
			return fiber.NewError(fiber.StatusInternalServerError, "failed to check user")
		}
	}

	now := time.Now().UTC()
	user.Name = req.Name
	user.Email = req.Email
	user.Avatar = req.Avatar
	user.UpdatedAt = now
	if req.Status != "" {
		user.Status = req.Status
	}
	if len(req.Roles) > 0 {
		user.Roles = req.Roles
	}
	if len(req.Types) > 0 {
		user.Types = req.Types
	}

	if err := db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Save(user).Error; err != nil {
			return err
		}
		if err := syncUserDetails(tx, user.ID, req.Details); err != nil {
			return err
		}
		if err := syncUserPreferences(tx, user.ID, req.Preferences); err != nil {
			return err
		}
		return nil
	}); err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to update user")
	}

	if err := db.Preload("Details").Preload("Preferences").First(user, user.ID).Error; err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to reload user")
	}

	return c.JSON(success(models.ToUserProfile(user)))
}

// DeleteUser removes a user and linked details/preferences through cascading delete rules.
func (uc *UserController) DeleteUser(c fiber.Ctx) error {
	db, err := dbFromCtx(c)
	if err != nil {
		return err
	}

	user, err := loadUserByID(c)
	if err != nil {
		return err
	}

	if err := db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Where("user_id = ?", user.ID).Delete(&models.UserDetails{}).Error; err != nil {
			return err
		}
		if err := tx.Where("user_id = ?", user.ID).Delete(&models.UserPreferences{}).Error; err != nil {
			return err
		}
		return tx.Delete(user).Error
	}); err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to delete user")
	}

	return c.JSON(success(SimpleResponse{Message: "user deleted"}))
}

func loadUserByID(c fiber.Ctx) (*models.User, error) {
	id, err := strconv.Atoi(c.Params("id"))
	if err != nil || id <= 0 {
		return nil, fiber.NewError(fiber.StatusBadRequest, "invalid user id")
	}

	db, err := dbFromCtx(c)
	if err != nil {
		return nil, err
	}

	var user models.User
	if err := db.Preload("Details").Preload("Preferences").First(&user, id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fiber.NewError(fiber.StatusNotFound, "user not found")
		}
		return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to load user")
	}

	return &user, nil
}

func loadUserByUUID(c fiber.Ctx) (*models.User, error) {
	uuid := c.Params("uuid")
	if uuid == "" {
		return nil, fiber.NewError(fiber.StatusBadRequest, "invalid user uuid")
	}

	db, err := dbFromCtx(c)
	if err != nil {
		return nil, err
	}

	var user models.User
	if err := db.Preload("Details").Preload("Preferences").First(&user, "uuid = ?", uuid).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fiber.NewError(fiber.StatusNotFound, "user not found")
		}
		return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to load user")
	}

	return &user, nil
}

func syncUserDetails(tx *gorm.DB, userID int, input *models.UserDetailsShort) error {
	if input == nil {
		return tx.Where("user_id = ?", userID).Delete(&models.UserDetails{}).Error
	}

	var details models.UserDetails
	if err := tx.Where("user_id = ?", userID).First(&details).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			details = models.UserDetails{UserID: userID}
		} else {
			return err
		}
	}

	details.Title = input.Title
	details.FirstName = input.FirstName
	details.LastName = input.LastName
	details.Address = input.Address
	details.City = input.City
	details.ZipCode = input.ZipCode
	details.Country = input.Country
	details.Phone = input.Phone

	if details.ID == 0 {
		return tx.Create(&details).Error
	}
	return tx.Save(&details).Error
}

func syncUserPreferences(tx *gorm.DB, userID int, input *models.UserPreferencesShort) error {
	if input == nil {
		return tx.Where("user_id = ?", userID).Delete(&models.UserPreferences{}).Error
	}

	var preferences models.UserPreferences
	if err := tx.Where("user_id = ?", userID).First(&preferences).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			preferences = models.UserPreferences{UserID: userID}
		} else {
			return err
		}
	}

	preferences.UseIdle = input.UseIdle
	preferences.IdleTimeout = input.IdleTimeout
	preferences.UseIdlePassword = input.UseIdlePassword
	preferences.IdlePin = input.IdlePin
	preferences.UseDirectLogin = input.UseDirectLogin
	preferences.UseQuadcodeLogin = input.UseQuadcodeLogin
	preferences.SendNoticesMail = input.SendNoticesMail
	preferences.Language = input.Language

	if preferences.ID == 0 {
		return tx.Create(&preferences).Error
	}
	return tx.Save(&preferences).Error
}
