package user

import (
	"crypto/rand"
	"encoding/json"
	"errors"
	"math/big"
	"regexp"
	"strings"
	"unicode"

	"git.umbach.dev/app-idea/rest-api/modules/config"
	"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"
	log "github.com/sirupsen/logrus"
	"gorm.io/gorm"
)

var cfg = &config.Cfg

func generateRandomString(n int, t int) (string, error) {
	var letters string

	if t == 1 {
		letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
	} else {
		letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	}

	r := make([]byte, n)

	for i := 0; i < n; i++ {
		num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
		if err != nil {
			return "", err
		}
		r[i] = letters[num.Int64()]
	}

	return string(r), nil
}

func generateRandomHashtag(db *gorm.DB, n int) (string, error) {
	c := make(chan bool)
	var s string
	var err error

	for {
		s, err = generateRandomString(6, 0)

		if err != nil {
			log.Warnln("error generating hashtag:", err)
			return "", err
		}

		go func() {
			c <- isHashtagValid(db, 0, s)
		}()

		if msg := <-c; msg {
			break
		}
	}

	return s, nil
}

func isHashtagValid(db *gorm.DB, t int, h string) bool {
	if t == 1 && !isUpper(h) || len(h) < 2 || len(h) > 6 {
		return false
	}

	var res string

	db.Raw("SELECT hashtag FROM users WHERE hashtag = ?", h).Scan(&res)

	if res == "" {
		return true
	} else {
		return false
	}
}

func isUpper(s string) bool {
	for _, r := range s {
		if !unicode.IsUpper(r) && unicode.IsLetter(r) {
			return false
		}
	}
	return true
}

func isUsernameValid(u string) bool {
	if len(u) < int(cfg.Settings.Lengths.UsernameMinLen) || len(u) > int(cfg.Settings.Lengths.UsernameMaxLen) {
		return false
	}
	return true
}

var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")

func isEmailValid(e string) bool {
	if len(e) < int(cfg.Settings.Lengths.EmailMinLen) || len(e) > int(cfg.Settings.Lengths.EmailMaxLen) {
		return false
	}
	return emailRegex.MatchString(e)
}

func isPasswordValid(p string) bool {
	if len(p) < int(cfg.Settings.Lengths.PasswordMinLen) || len(p) > int(cfg.Settings.Lengths.PasswordMaxLen) {
		return false
	}
	return true
}

func isEmailAvailable(db *gorm.DB, email string) bool {
	var res string

	db.Raw("SELECT email FROM users WHERE email = ?", email).Scan(&res)

	if res == "" {
		return true
	} else {
		return false
	}
}

func getUserIdBySessionId(sessionId string) (string, error) {
	db := database.DB
	session := structs.Session{}

	db.Select("user_id").Where(cfg.Settings.Cookies.SessionId+" = ?", sessionId).Find(&session)

	return session.UserId, nil
}

func GetUserById(c *fiber.Ctx) error {
	// swagger:operation GET /users User user
	// ---
	// summary: Informations about an user by id (except password)
	// parameters:
	// - name: v
	//   in: query
	//   description: Example -> { "v"; ["name", "state", "language_id"] }
	//   type: string
	//   required: true
	// responses:
	//   '200':
	//     description: User informations
	//   '400':
	//     description: Values wrong format

	return userInfos(c, c.Params("id"))
}

func GetUser(c *fiber.Ctx) error {
	// swagger:operation GET /user User user
	// ---
	// summary: Informations about the user (except password)
	// parameters:
	// - name: v
	//   in: query
	//   description: Example -> { "v"; ["name", "state", "language_id"] }
	//   type: string
	//   required: true
	// responses:
	//   '200':
	//     description: User informations
	//   '400':
	//     description: Values wrong format

	return userInfos(c, "")
}

func userInfos(c *fiber.Ctx, userId string) error {
	type Input struct {
		Values []string `json:"v"`
	}

	var input Input

	if err := c.BodyParser(&input); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	value := strings.Join(input.Values, ",")

	if strings.Contains(value, "password") {
		return c.SendStatus(fiber.StatusForbidden)
	}

	db := database.DB

	if userId == "" {
		var err error

		userId, err = getUserIdBySessionId(c.Cookies(cfg.Settings.Cookies.SessionId))

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

	user := map[string]interface{}{}

	res := db.Model(&structs.User{}).Select(value).First(&user, "id = ?", userId)

	if res.Error != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	return c.JSON(&user)
}

func getUserBySessionId(sessionId string) (structs.User, error) {
	db := database.DB
	user := structs.User{}
	userId, err := getUserIdBySessionId(sessionId)

	if err != nil {
		return user, err
	}

	err = db.Where("id = ?", userId).First(&user).Error

	if errors.Is(err, gorm.ErrRecordNotFound) {
		return user, err
	}

	log.Infoln("getUserBySessionId", user)

	return user, nil
}

func deleteUser(c *fiber.Ctx) error {
	userAction := structs.UserAction{Id: c.Params("actionId")}

	db := database.DB

	db.First(&userAction)

	db.Delete(&structs.User{Id: userAction.UserId})
	db.Where("user_id = ?", userAction.UserId).Delete(&structs.Session{})
	db.Delete(&userAction)

	return c.SendStatus(fiber.StatusOK)
}

func DeleteUser(c *fiber.Ctx) error {
	user, err := getUserBySessionId(c.Cookies(cfg.Settings.Cookies.SessionId))

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

	userActivationId, err := createUserAction(user.Id)

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

	user.State = 2

	db := database.DB

	db.Save(&user)

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

	return c.SendStatus(fiber.StatusOK)
}

/*
func GetUsers(c *fiber.Ctx) error {

	list := []string{}
	/*
		var (
			name string
		)*/
/*
	rows, err := db.Query("SELECT username FROM users;")
	fmt.Println("err", err)
	defer rows.Close()
	fmt.Println("reading data:")
	for rows.Next() {
		err := rows.Scan(&name)
		fmt.Printf("Data row = (%s, %s)\n", name, err)
		list = append(list, name)
	}
	err = rows.Err()*/
/*
	return c.JSON(list)
}
*/
