package handlers

import (
	"context"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/timetracker/backend/internal/api/dto"
	"github.com/timetracker/backend/internal/api/middleware"
	"github.com/timetracker/backend/internal/api/responses"
	"github.com/timetracker/backend/internal/api/utils"
	"github.com/timetracker/backend/internal/models"
	"github.com/timetracker/backend/internal/types"
)

// UserHandler handles user-related API endpoints
type UserHandler struct{}

// NewUserHandler creates a new UserHandler
func NewUserHandler() *UserHandler {
	return &UserHandler{}
}

// GetUsers handles GET /users
//
//	@Summary		Get all users
//	@Description	Get a list of all users
//	@Tags			users
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Success		200	{object}	utils.Response{data=[]dto.UserDto}
//	@Failure		401	{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500	{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/users [get]
func (h *UserHandler) GetUsers(c *gin.Context) {
	utils.HandleGetAll(c, models.GetAllUsers, convertUserToDTO, "users")
}

// GetUserByID handles GET /users/:id
//
//	@Summary		Get user by ID
//	@Description	Get a user by their ID
//	@Tags			users
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Param			id	path		string	true	"User ID"
//	@Success		200	{object}	utils.Response{data=dto.UserDto}
//	@Failure		400	{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		401	{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		404	{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500	{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/users/{id} [get]
func (h *UserHandler) GetUserByID(c *gin.Context) {
	// We need a custom wrapper for GetUserByID because the ID parameter is parsed differently
	id, err := utils.ParseID(c, "id")
	if err != nil {
		responses.BadRequestResponse(c, "Invalid user ID format")
		return
	}

	// Get user from the database
	user, err := models.GetUserByID(c.Request.Context(), id)
	if err != nil {
		responses.InternalErrorResponse(c, "Error retrieving user: "+err.Error())
		return
	}

	if user == nil {
		responses.NotFoundResponse(c, "User not found")
		return
	}

	// Convert to DTO
	userDTO := convertUserToDTO(user)

	responses.SuccessResponse(c, http.StatusOK, userDTO)
}

// CreateUser handles POST /users
//
//	@Summary		Create a new user
//	@Description	Create a new user
//	@Tags			users
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Param			user	body		dto.UserCreateDto	true	"User data"
//	@Success		201		{object}	utils.Response{data=dto.UserDto}
//	@Failure		400		{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		401		{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500		{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/users [post]
func (h *UserHandler) CreateUser(c *gin.Context) {
	utils.HandleCreate(c, createUserWrapper, convertUserToDTO, "user")
}

// UpdateUser handles PUT /users/:id
//
//	@Summary		Update a user
//	@Description	Update an existing user
//	@Tags			users
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Param			id		path		string				true	"User ID"
//	@Param			user	body		dto.UserUpdateDto	true	"User data"
//	@Success		200		{object}	utils.Response{data=dto.UserDto}
//	@Failure		400		{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		401		{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		404		{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500		{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/users/{id} [put]
func (h *UserHandler) UpdateUser(c *gin.Context) {
	utils.HandleUpdate(c, models.UpdateUser, convertUserToDTO, prepareUserUpdate, "user")
}

// DeleteUser handles DELETE /users/:id
//
//	@Summary		Delete a user
//	@Description	Delete a user by their ID
//	@Tags			users
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Param			id	path		string	true	"User ID"
//	@Success		204	{object}	utils.Response
//	@Failure		400	{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		401	{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500	{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/users/{id} [delete]
func (h *UserHandler) DeleteUser(c *gin.Context) {
	utils.HandleDelete(c, models.DeleteUser, "user")
}

// Login handles POST /auth/login
//
//	@Summary		Login
//	@Description	Authenticate a user and get a JWT token
//	@Tags			auth
//	@Accept			json
//	@Produce		json
//	@Param			credentials	body		dto.LoginDto	true	"Login credentials"
//	@Success		200			{object}	utils.Response{data=dto.TokenDto}
//	@Failure		400			{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		401			{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500			{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/auth/login [post]
func (h *UserHandler) Login(c *gin.Context) {
	// Parse request body
	var loginDTO dto.LoginDto
	if err := c.ShouldBindJSON(&loginDTO); err != nil {
		responses.BadRequestResponse(c, "Invalid request body: "+err.Error())
		return
	}

	// Authenticate user
	user, err := models.AuthenticateUser(c.Request.Context(), loginDTO.Email, loginDTO.Password)
	if err != nil {
		responses.UnauthorizedResponse(c, "Invalid login credentials")
		return
	}

	// Generate JWT token
	token, err := middleware.GenerateToken(user, c)
	if err != nil {
		responses.InternalErrorResponse(c, "Error generating token: "+err.Error())
		return
	}

	// Return token
	tokenDTO := dto.TokenDto{
		Token: token,
		User:  convertUserToDTO(user),
	}

	responses.SuccessResponse(c, http.StatusOK, tokenDTO)
}

// Register handles POST /auth/register
//
//	@Summary		Register
//	@Description	Register a new user and get a JWT token
//	@Tags			auth
//	@Accept			json
//	@Produce		json
//	@Param			user	body		dto.UserCreateDto	true	"User data"
//	@Success		201		{object}	utils.Response{data=dto.TokenDto}
//	@Failure		400		{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500		{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/auth/register [post]
func (h *UserHandler) Register(c *gin.Context) {
	// Parse request body
	var userCreateDTO dto.UserCreateDto
	if err := c.ShouldBindJSON(&userCreateDTO); err != nil {
		responses.BadRequestResponse(c, "Invalid request body: "+err.Error())
		return
	}

	// Convert DTO to model
	userCreate := convertCreateDTOToModel(userCreateDTO)

	// Create user in the database
	user, err := models.CreateUser(c.Request.Context(), userCreate)
	if err != nil {
		responses.InternalErrorResponse(c, "Error creating user: "+err.Error())
		return
	}

	// Generate JWT token
	token, err := middleware.GenerateToken(user, c)
	if err != nil {
		responses.InternalErrorResponse(c, "Error generating token: "+err.Error())
		return
	}

	// Return token
	tokenDTO := dto.TokenDto{
		Token: token,
		User:  convertUserToDTO(user),
	}

	responses.SuccessResponse(c, http.StatusCreated, tokenDTO)
}

// GetCurrentUser handles GET /auth/me
//
//	@Summary		Get current user
//	@Description	Get the currently authenticated user
//	@Tags			auth
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Success		200	{object}	utils.Response{data=dto.UserDto}
//	@Failure		401	{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500	{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/auth/me [get]
func (h *UserHandler) GetCurrentUser(c *gin.Context) {
	// Get user ID from context (set by AuthMiddleware)
	userID, err := middleware.GetUserIDFromContext(c)
	if err != nil {
		responses.UnauthorizedResponse(c, "User not authenticated")
		return
	}

	// Get user from the database
	user, err := models.GetUserByID(c.Request.Context(), userID)
	if err != nil {
		responses.InternalErrorResponse(c, "Error retrieving user: "+err.Error())
		return
	}

	if user == nil {
		responses.NotFoundResponse(c, "User not found")
		return
	}

	// Convert to DTO
	userDTO := convertUserToDTO(user)

	responses.SuccessResponse(c, http.StatusOK, userDTO)
}

// Helper functions for DTO conversion

func convertUserToDTO(user *models.User) dto.UserDto {
	var companyID *string
	if user.CompanyID != nil {
		s := user.CompanyID.String()
		companyID = &s
	}
	return dto.UserDto{
		ID:         user.ID.String(),
		CreatedAt:  user.CreatedAt,
		UpdatedAt:  user.UpdatedAt,
		Email:      user.Email,
		Role:       user.Role,
		CompanyID:  companyID,
		HourlyRate: user.HourlyRate,
	}
}

// prepareUserUpdate prepares the user update object by parsing the ID, binding the JSON, and converting the DTO to a model
func prepareUserUpdate(c *gin.Context) (models.UserUpdate, error) {
	// Parse ID from URL
	idStr := c.Param("id")
	id, err := types.ULIDFromString(idStr)
	if err != nil {
		responses.BadRequestResponse(c, "Invalid user ID format")
		return models.UserUpdate{}, err
	}

	// Parse request body
	var userUpdateDTO dto.UserUpdateDto
	if err := utils.BindJSON(c, &userUpdateDTO); err != nil {
		responses.BadRequestResponse(c, err.Error())
		return models.UserUpdate{}, err
	}

	// Convert DTO to Model
	update := models.UserUpdate{
		ID: id,
	}

	if userUpdateDTO.Email != nil {
		update.Email = userUpdateDTO.Email
	}
	if userUpdateDTO.Password != nil {
		update.Password = userUpdateDTO.Password
	}
	if userUpdateDTO.Role != nil {
		update.Role = userUpdateDTO.Role
	}

	if userUpdateDTO.CompanyID.Valid {
		if userUpdateDTO.CompanyID.Value != nil {
			companyID, err := types.ULIDFromString(*userUpdateDTO.CompanyID.Value)
			if err != nil {
				responses.BadRequestResponse(c, "Invalid company ID format")
				return models.UserUpdate{}, err
			}
			update.CompanyID = types.NewNullable(companyID)
		} else {
			update.CompanyID = types.Null[types.ULID]()
		}
	}

	if userUpdateDTO.HourlyRate != nil {
		update.HourlyRate = userUpdateDTO.HourlyRate
	}

	return update, nil
}

func convertCreateDTOToModel(dto dto.UserCreateDto) models.UserCreate {
	var companyID *types.ULID
	if dto.CompanyID != nil {
		wrapper, _ := types.ULIDFromString(*dto.CompanyID) // Ignoring error, validation happens in the model
		companyID = &wrapper
	}

	return models.UserCreate{
		Email:      dto.Email,
		Password:   dto.Password,
		Role:       dto.Role,
		CompanyID:  companyID,
		HourlyRate: dto.HourlyRate,
	}
}

// createUserWrapper is a wrapper function for models.CreateUser that takes a DTO as input
func createUserWrapper(ctx context.Context, createDTO dto.UserCreateDto) (*models.User, error) {
	// Convert DTO to model
	userCreate := convertCreateDTOToModel(createDTO)

	// Call the original function
	return models.CreateUser(ctx, userCreate)
}
