package main

import (
	"context"
	"errors"
	"log"
	"net/http"
	"strconv"

	"github.com/FernandoVideira/LK_API_Temp/internal/store"
	"github.com/go-chi/chi/v5"
)

type userKey string

const userContextKey userKey = "user"

// GetUser godoc
//
//	@Summary		Fetches a user Profile
//	@Description	Fetches a user Profile by ID
//	@Tags			users
//	@Accept			json
//	@Produce		json
//	@Param			id	path		int	true	"User ID"
//	@Success		200	{object}	store.User
//	@Failure		400	{object}	error
//	@Failure		404	{object}	error
//	@Failure		500	{object}	error
//	@Security		ApiKeyAuth
//	@Router			/users/{id} [get]
func (api *api) getUserHandler(w http.ResponseWriter, r *http.Request) {
	userID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	if err != nil || userID < 1 {
		api.badRequestError(w, r, err)
		return
	}
	ctx := r.Context()

	user, err := api.getUser(ctx, userID)
	if err != nil {
		switch err {
		case store.ErrNotFound:
			api.notFoundError(w, r, err)
			return
		default:
			api.internalServerError(w, r, err)
			return
		}
	}

	if err := api.jsonResponse(w, http.StatusOK, user); err != nil {
		api.internalServerError(w, r, err)
	}
}

type updateUserPayload struct {
	FirstName *string `json:"first_name" validate:"omitempty,max=100"`
	LastName  *string `json:"last_name" validate:"omitempty,max=100"`
	Username  *string `json:"username" validate:"omitempty,max=100"`
	Email     *string `json:"email" validate:"omitempty,email,max=255"`
}

// Update godoc
//
//	@Summary		Update a user Profile
//	@Description	Update a user Profile by ID
//	@Tags			users
//	@Accept			json
//	@Produce		json
//	@Param			id		path		int					true	"User ID"
//	@Param			payload	body		updateUserPayload	true	"User Profile"
//	@Success		200		{object}	store.User
//	@Failure		400		{object}	error
//	@Failure		404		{object}	error
//	@Failure		500		{object}	error
//	@Security		ApiKeyAuth
//	@Router			/users/{id} [patch]
func (api *api) updateUserHandler(w http.ResponseWriter, r *http.Request) {
	user := getUserFromContext(r)
	log.Println(user)

	var payload updateUserPayload
	if err := readJSON(w, r, &payload); err != nil {
		api.badRequestError(w, r, err)
		return
	}

	user = validateUser(payload, user)

	if err := api.updateUser(r.Context(), user); err != nil {
		api.internalServerError(w, r, err)
		return
	}

	if err := api.jsonResponse(w, http.StatusOK, user); err != nil {
		api.internalServerError(w, r, err)
	}
}

func (api *api) updateUser(ctx context.Context, user *store.User) error {
	if err := api.store.Users.Update(ctx, user); err != nil {
		return err
	}

	api.cacheStore.Users.Delete(ctx, user.ID)
	return nil
}

// FollowUser godoc
//
//	@Summary		Follows a user
//	@Description	Follows a user by ID
//	@Tags			users
//	@Accept			json
//	@Produce		json
//	@Param			id	path		int		true	"User ID"
//	@Success		204	{string}	string	"User followed"
//	@Failure		400	{object}	error	"User not found"
//	@Router			/users/{id}/follow [put]
func (api *api) followUserHandler(w http.ResponseWriter, r *http.Request) {
	followee := getUserFromContext(r)
	followedID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	if err != nil {
		api.badRequestError(w, r, err)
		return
	}

	if err := api.store.Followers.Follow(r.Context(), followee.ID, followedID); err != nil {
		switch {
		case errors.Is(err, store.ErrConflict):
			api.conflictError(w, r, err)
			return
		default:
			api.internalServerError(w, r, err)
			return
		}
	}

	if err := api.jsonResponse(w, http.StatusNoContent, nil); err != nil {
		api.internalServerError(w, r, err)
	}
}

// UnollowUser godoc
//
//	@Summary		Unfollows a user
//	@Description	Unfollows a user by ID
//	@Tags			users
//	@Accept			json
//	@Produce		json
//	@Param			id	path		int		true	"User ID"
//	@Success		204	{string}	string	"User unfollowed"
//	@Failure		400	{object}	error	"User payload error"
//	@Failure		404	{object}	error	"User not found"
//	@Router			/users/{id}/unfollow [put]
func (api *api) unfollowUserHandler(w http.ResponseWriter, r *http.Request) {
	followee := getUserFromContext(r)
	followedUserID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	if err != nil {
		api.badRequestError(w, r, err)
		return
	}

	if err := api.store.Followers.Unfollow(r.Context(), followee.ID, followedUserID); err != nil {
		switch {
		case errors.Is(err, store.ErrNotFound):
			api.conflictError(w, r, err)
			return
		default:
			api.internalServerError(w, r, err)
			return
		}
	}

	w.WriteHeader(http.StatusNoContent)
	if err := api.jsonResponse(w, http.StatusNoContent, nil); err != nil {
		api.internalServerError(w, r, err)
	}
}

// ActivateUser godoc
//
//	@Summary		Activate/Register a user
//	@Description	Activate/Register a user by invitation token
//	@Tags			users
//	@Accept			json
//	@Produce		json
//	@Param			token	path		string	true	"Invitation Token"
//	@Success		204		{string}	string	"User activated"
//	@Failure		404		{object}	error	"User not found"
//	@Failure		500		{object}	error	"Internal Server Error"
//	@Security		ApiKeyAuth
//	@Router			/users/activate/{token} [put]
func (api *api) activateUserHandler(w http.ResponseWriter, r *http.Request) {
	token := chi.URLParam(r, "token")

	err := api.store.Users.Activate(r.Context(), token)
	if err != nil {
		switch err {
		case store.ErrNotFound:
			api.notFoundError(w, r, err)
			return
		default:
			api.internalServerError(w, r, err)
			return
		}
	}

	w.WriteHeader(http.StatusNoContent)
	if err := api.jsonResponse(w, http.StatusNoContent, ""); err != nil {
		api.internalServerError(w, r, err)
	}
}

type updateUserPasswordPayload struct {
	CurrentPassword string `json:"current_password" validate:"required"`
	NewPassword     string `json:"new_password" validate:"required,password,nefield=CurrentPassword"`
}

// UpdatePassword godoc
//
//	@Summary		Update user password
//	@Description	Update user password with verification
//	@Description	Password must:
//	@Description	- Be at least 8 characters long
//	@Description	- Contain at least one uppercase letter
//	@Description	- Contain at least one lowercase letter
//	@Description	- Contain at least one number
//	@Description	- Contain at least one special character
//	@Description	- Be different from the current password
//	@Tags			users
//	@Accept			json
//	@Produce		json
//	@Param			id		path		int							true	"User ID"
//	@Param			payload	body		updateUserPasswordPayload	true	"Password Update"
//	@Success		204		{string}	string						"Password updated successfully"
//	@Failure		400		{object}	error						"Invalid request"
//	@Failure		401		{object}	error						"Unauthorized"
//	@Failure		404		{object}	error						"User not found"
//	@Failure		500		{object}	error						"Internal server error"
//	@Security		ApiKeyAuth
//	@Router			/users/{id}/password [patch]
func (api *api) updatePasswordHandler(w http.ResponseWriter, r *http.Request) {
	user := getUserFromContext(r)

	var payload updateUserPasswordPayload
	if err := readJSON(w, r, &payload); err != nil {
		api.badRequestError(w, r, err)
		return
	}

	if err := Validate.Struct(payload); err != nil {
		api.badRequestError(w, r, err)
		return
	}

	if err := user.Password.Compare(payload.CurrentPassword); err != nil {
		api.unauthorizedError(w, r, err)
		return
	}

	if err := user.Password.Set(payload.NewPassword); err != nil {
		api.internalServerError(w, r, err)
		return
	}

	if err := api.updateUser(r.Context(), user); err != nil {
		api.internalServerError(w, r, err)
		return
	}

	w.WriteHeader(http.StatusNoContent)
	if err := api.jsonResponse(w, http.StatusNoContent, ""); err != nil {
		api.internalServerError(w, r, err)
	}
}

func validateUser(payload updateUserPayload, usrToUpdate *store.User) *store.User {
	if payload.Email != nil {
		usrToUpdate.Email = *payload.Email
	}

	if payload.FirstName != nil {
		usrToUpdate.FirstName = *payload.FirstName
	}

	if payload.LastName != nil {
		usrToUpdate.LastName = *payload.LastName
	}

	if payload.Username != nil {
		usrToUpdate.Username = *payload.Username
	}

	return usrToUpdate
}

func getUserFromContext(r *http.Request) *store.User {
	user, _ := r.Context().Value(userContextKey).(*store.User)
	return user
}
