package user

import (
	"encoding/base64"
	"encoding/json"
	"strings"
	"time"

	"git.umbach.dev/app-idea/rest-api/modules/database"
	"git.umbach.dev/app-idea/rest-api/modules/rabbitmq"
	"git.umbach.dev/app-idea/rest-api/modules/structs"
	"github.com/gofiber/fiber/v2"
	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"
	"golang.org/x/crypto/bcrypt"
)

type RegisterInput struct {
	Username   string `json:"username"`
	Email      string `json:"email"`
	Password   string `json:"password"`
	Hashtag    string `json:"hashtag"`
	LanguageId int    `json:"languageId"`
}

func NewUser(c *fiber.Ctx) error {
	// swagger:operation POST /users User user
	// ---
	// summary: Create new user
	// produces:
	// - application/json
	// parameters:
	// - name: username
	//   in: query
	//   description: username of the user (length 3-30)
	//   type: string
	//   required: true
	// - name: email
	//   in: query
	//   description: email of the user (length 3-255)
	//   type: string
	//   required: true
	// - name: password
	//   in: query
	//   description: password (base64) of the user (length 6-250)
	//   type: string
	//   required: true
	// - name: hashtag
	//   in: query
	//   description: hashtag of the client (length 2-6, UPPERCASE (Letters, Numbers))
	//   type: string
	// - name: avatar_url
	//   in: query
	//   description: avatar url of the client
	//   type: string
	// - name: location
	//   in: query
	//   description: location of the client (length 1-20) (for example Frankfurt)
	//   type: string
	// responses:
	//   '201':
	//     "$ref": "#/definitions/User"
	//   '400':
	//     description: format is not correct
	//   '422':
	//     description: username, email or/and hashtag already assigned

	var input RegisterInput

	if err := c.BodyParser(&input); err != nil {
		log.Debugln("bodyParser failed:", err)
		return c.SendStatus(fiber.StatusBadRequest)
	}

	decodedPassword, err := base64.StdEncoding.DecodeString(input.Password)

	if err != nil {
		log.Debugln("base64 decoding failed:", err)
		return c.SendStatus(fiber.StatusBadRequest)
	}

	input.Password = string(decodedPassword)

	if !isUsernameValid(input.Username) || !isEmailValid(input.Email) || !isPasswordValid(input.Password) {
		return c.SendStatus(fiber.StatusForbidden)
	}

	user := structs.User{Email: input.Email}
	db := database.DB

	if !isEmailAvailable(db, user.Email) {
		return c.SendStatus(fiber.StatusUnprocessableEntity)
	}

	if input.Hashtag == "" {
		input.Hashtag, err = generateRandomHashtag(db, 6)

		if err != nil {
			return c.SendStatus(fiber.StatusInternalServerError)
		}
	} else if !isHashtagValid(db, 1, input.Hashtag) {
		return c.SendStatus(fiber.StatusUnprocessableEntity)
	}

	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(input.Password), bcrypt.DefaultCost)

	if err != nil {
		log.Warnln("Failed to bcrypt password", err)
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	now := time.Now()

	user.Id = strings.Replace(uuid.New().String(), "-", "", -1)
	user.Hashtag = input.Hashtag
	user.Name = input.Username
	user.Password = string(hashedPassword)
	user.LanguageId = input.LanguageId
	user.State = 1
	user.LastLogin = now
	user.CreatedAt = now

	res := db.Create(&user)

	if res.Error != nil {
		log.Warnln("Failed to insert user to db:", res.Error)
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	userActionId, err := createUserAction(user.Id)

	if err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	rabbitmq.PublishMail(user.Email, 0, user.LanguageId, json.RawMessage(`{"name": "`+user.Name+`",
		"email": "`+user.Email+`",
		"url": "http://localhost:3000/api/v1/user/action/0/`+userActionId+`"}`))

	sessionId, err := createUserSession(db, user.Id, c.IP(), string(c.Context().UserAgent()))

	if err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	expires := getUserSessionExpiresTime()
	cfg := cfg.Settings.Cookies

	c.Cookie(&fiber.Cookie{Name: cfg.SessionId, Value: sessionId, Secure: true, HTTPOnly: true, Expires: expires})
	c.Cookie(&fiber.Cookie{Name: cfg.Username, Value: input.Username, Secure: true, Expires: expires})
	c.Cookie(&fiber.Cookie{Name: cfg.UserHashtag, Value: input.Hashtag, Secure: true, Expires: expires})

	log.Debugln("user created", user)

	return c.SendStatus(fiber.StatusCreated)
}

func ActivateUser(c *fiber.Ctx) error {
	// swagger:operation POST /user/action/{actionType}/{actionId} User activation
	// ---
	// summary: Activate user
	// parameters:
	// - name: id
	//   in: query
	//   description: action id
	//   type: string
	//   required: true
	// responses:
	//   '200':
	//     description: User was activated
	//   '401':
	//     description: action Id is incorrect or expired

	db := database.DB

	deleteExpiredUserActions(db)

	userAction := structs.UserAction{Id: c.Params("actionId")}

	log.Infoln("activate user", c.Params("actionType"), c.Params("actionId"))

	db.Select("user_id").Where("id = ?", c.Params("actionId")).Find(&userAction)

	log.Infoln("usA", userAction)

	if userAction.UserId == "" {
		log.Info("StatusUnauthorized")
		return c.SendStatus(fiber.StatusUnauthorized)
	}

	db.Delete(userAction)

	db.Model(structs.User{Id: userAction.UserId}).Update("state", 0)

	return c.SendStatus(fiber.StatusOK)
}
