package main

import (
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/FernandoVideira/LK_API_Temp/internal/mailer"
	"github.com/FernandoVideira/LK_API_Temp/internal/store"
	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
)

type RegisterUserPayload struct {
	FirstName string `json:"first_name" validate:"required,max=100"`
	LastName  string `json:"last_name" validate:"required,max=100"`
	Username  string `json:"username" validate:"required,max=100"`
	Email     string `json:"email" validate:"required,email,max=255"`
	Password  string `json:"password" validate:"required,password"`
}

type UserWithToken struct {
	*store.User
	Token string `json:"token"`
}

// RegisterUser godoc
//
//	@Summary		Register a new User
//	@Description	Register a new User
//	@Tags			authentication
//	@Accept			json
//	@Produce		json
//	@Param			payload	body		RegisterUserPayload	true	"User Credentials"
//	@Success		201		{object}	UserWithToken		"User Registered"
//	@Failure		400		{object}	error				"User payload error"
//	@Failure		500		{object}	error				"Internal Server Error"
//	@Router			/authentication/user [post]
func (api *api) registerUserHandler(w http.ResponseWriter, r *http.Request) {
	var payload RegisterUserPayload
	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
	}

	user := &store.User{
		FirstName: payload.FirstName,
		LastName:  payload.LastName,
		Username:  payload.Username,
		Email:     payload.Email,
		Role: store.Role{
			Name: "user",
		},
	}

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

	ctx := r.Context()

	plainToken := uuid.New().String()

	hash := sha256.Sum256([]byte(plainToken))
	hashedToken := hex.EncodeToString(hash[:])

	// Store the user
	err := api.store.Users.CreateAndInvite(ctx, user, hashedToken, api.config.mail.exp)
	if err != nil {
		switch {
		case errors.Is(err, store.ErrDuplicateEmail):
			api.badRequestError(w, r, err)
		case errors.Is(err, store.ErrDuplicateUsername):
			api.badRequestError(w, r, err)
		default:
			api.internalServerError(w, r, err)
		}
		return
	}

	userWithToken := UserWithToken{
		User:  user,
		Token: plainToken,
	}

	activationURL := fmt.Sprintf("%s/confirm/%s", api.config.frontEndURL, plainToken)

	isProdEnv := api.config.env == "production"
	vars := struct {
		ServiceName   string
		Username      string
		ActivationURL string
	}{
		ServiceName:   "LK_API_Temp",
		Username:      user.Username,
		ActivationURL: activationURL,
	}

	// Send Email
	status, err := api.mailer.Send(mailer.UserWelcomeTemplate, user.Username, user.Email, vars, !isProdEnv)
	if err != nil {
		// Log the error
		api.logger.Errorw("failed to send welcome email", "error", err)

		//Rollback the user creation if the email fails
		if err := api.store.Users.Delete(ctx, user.ID); err != nil {
			api.logger.Errorw("failed to rollback user creation", "error", err)
		}

		api.internalServerError(w, r, err)
		return
	}

	api.logger.Infow("email sent", "status code", status)

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

type CredentialsPayload struct {
	Email    string `json:"email" validate:"required,email,max=255"`
	Password string `json:"password" validate:"required,min=3,max=72"`
}

// CreateTokenHandler godoc
//
//	@Summary		Create a new token
//	@Description	Create a new token for the user
//	@Tags			authentication
//	@Accept			json
//	@Produce		json
//	@Param			payload	body		CredentialsPayload	true	"User Credentials"
//	@Success		201		{string}	string				"Token Created"
//	@Failure		400		{object}	error
//	@Failure		401		{object}	error
//	@Failure		500		{object}	error
//	@Router			/authentication/token [post]
func (api *api) createTokenHandler(w http.ResponseWriter, r *http.Request) {
	//* parse payload credentials
	var payload CredentialsPayload
	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
	}

	//* fetch the user (check if the user exists) from the payload
	user, err := api.store.Users.GetByEmail(r.Context(), payload.Email)
	if err != nil {
		switch err {
		case store.ErrNotFound:
			api.unauthorizedError(w, r, err)
		default:
			api.internalServerError(w, r, err)
		}
		return
	}

	//* compare the password from the payload with the user password
	if err := user.Password.Compare(payload.Password); err != nil {
		api.unauthorizedError(w, r, err)
		return
	}

	claims := &jwt.MapClaims{
		"sub": user.ID,
		"exp": time.Now().Add(api.config.auth.token.exp).Unix(),
		"iat": time.Now().Unix(),
		"nbf": time.Now().Unix(),
		"iss": api.config.auth.token.iss,
		"aud": api.config.auth.token.aud,
	}

	//* generate a new token -> add the claims
	token, err := api.authenticator.GenerateToken(claims)
	if err != nil {
		api.internalServerError(w, r, err)
		return
	}

	//* send the token back to the user

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