package main

import (
  "github.com/kataras/iris"
  "github.com/kataras/go-template/html"
  "fmt"
  "git.mmnx.de/Moe/databaseutils"
  "git.mmnx.de/Moe/usermanager"
  "git.mmnx.de/Moe/configutils"
  "git.mmnx.de/Moe/templatehelpers"
  "golang.org/x/crypto/bcrypt"
  "errors"
)

type pageUserParams struct{
  HasError string
  Error string
  ReqDir string
  Username string
  Email string
  Admin string
  } // {Error: ""}

func main() {

  conf := configutils.ReadConfig("config.json") // read config
  configutils.Conf = &conf // store conf globally accessible

  databaseutils.DBUtil = &databaseutils.DBUtils{configutils.Conf.DBUser, configutils.Conf.DBPass, configutils.Conf.DBHost, configutils.Conf.DBName, nil} // init dbutils
  databaseutils.DBUtil.Connect() // connect to db

  users := make([]usermanager.User, 0) // users list
  usermanager.Users = &users // store globally accessible

  fmt.Print("") // for not needing to remove fmt ...

  iris.Config.IsDevelopment = true
  //iris.Config.Render.Template.Gzip = true

  /** HELPER FUNCTION EXAMPLE **/
    /*config := html.DefaultConfig()
    config.Layout = "layouts/main.html"
    config.Helpers["boldme"] = func(input string) raymond.SafeString {
      return raymond.SafeString("<b> " + input + "</b>")
    }*/

  /** ROUTING **/

  iris.UseTemplate(html.New(html.Config{
    Layout: "layouts/main.html",
  }))

  iris.Static("/js", "./static/js", 1)
  iris.Static("/css", "./static/css", 1)
  iris.Static("/img", "./static/img", 1)
  iris.Static("/static", "./static/static", 1)

  iris.Post("/login", loginHandler) // login form handler // TODO: outsource ?
  iris.Post("/register", registerHandler) // TODO outsource ?
  iris.Post("/account", usermanager.AuthHandler, accountUpdateHandler)
  iris.Post("/admin", usermanager.AuthHandler, usermanager.AdminHandler, adminPostHandler)

  iris.Get("/login", templateHandler) // TODO not when logged in
  iris.Get("/register", templateHandler) // TODO not when logged in
	iris.Get("/", usermanager.AuthHandler, templateHandler)
  iris.Get("/account", usermanager.AuthHandler, templateHandler)
  iris.Get("/help", usermanager.AuthHandler, templateHandler)
  iris.Get("/admin", usermanager.AuthHandler, usermanager.AdminHandler, templateHandler)

  /** OTHER **/

  iris.Listen(":8080")
}

func loginHandler(ctx *iris.Context) {
  username := ctx.FormValueString("username") // POST values from login form
  password := ctx.FormValueString("password")

  user := usermanager.User{} // new user
  tokenString, err := user.Login(username, password) // try to login

  if err != nil {
    ctx.Render("login_box.html", usermanager.PageParams{"1", err.Error(), "login", "0"})
  } else {
    ctx.SetCookieKV("token", tokenString)
    ctx.Redirect("/")
    // TODO: error-alternative success (main.html)
  }
}

func registerHandler(ctx *iris.Context) {
  token := ctx.FormValueString("token") // POST values from login form
  username := ctx.FormValueString("username")
  password := ctx.FormValueString("password")

  user := usermanager.User{} // new user

  tokens := usermanager.GetTokens(false) // get all unused tokens
  validToken := false
  for i, _ := range tokens {
    if token == tokens[i] {
      validToken = true
      break
    }
  }
  if !validToken { // token not valid
    templatehelpers.ShowError(usermanager.ERR_INVALID_TOKEN, ctx, "register")
  }

  userID := usermanager.SearchUserByUsernameInDB(username) // check if a user with that name already exists
  if userID != -1 {
    templatehelpers.ShowError(usermanager.ERR_USERNAME_TAKEN, ctx, "register")
  }

  passwordBin, _ := bcrypt.GenerateFromPassword([]byte(password), 15) // hash password

  err := usermanager.RegisterUserWithToken(username, string(passwordBin), token) // register user
  if err != nil {
    templatehelpers.ShowError(err.Error(), ctx, "register")
    return
  }

  tokenString, err := user.Login(username, password) // try to login

  if err != nil {
    templatehelpers.ShowError(err.Error(), ctx, "login")
  } else {
    ctx.SetCookieKV("token", tokenString)
    ctx.Redirect("/")
    // TODO: error-alternative success (main.html)
  }
}

func accountUpdateHandler(ctx *iris.Context) { // TODO tidy up?
  err := errors.New(""); err = nil
  username := ctx.FormValueString("username") // POST values
  password := ctx.FormValueString("password")
  userID := ctx.GetString("userID")
  usersArrayID := usermanager.SearchUser(userID)
  user := (*usermanager.Users)[usersArrayID] // user must be logged in to do this -> get from users list

  if username != "" && usermanager.SearchUserByUsername(username) != -1 && username != user.Username { // username can't be changed as there already exists a user with that name or it's the old name
    ctx.Render("account_box.html", usermanager.PageUserParams{"1", errors.New("Username already taken").Error(), "account", user.Username, user.Admin, []string{"ayy", "lmao"}})
    return
  }

  needQuery := false

  if username != "" { // if not left empty (-> change)
    needQuery = true
  } else {
    username = user.Username // keep
  }

  hashedPassword := user.Password // we assumpt the user's not changing his password

  if password != "" { // if not left empty we change it
    needQuery = true
    hashedPassword, err = func (hashedPassword []byte, err error) (string, error) { // hash password, we use an anonymous function to convert it to string
      if err != nil { // should never happen
        ctx.Render("account_box.html", usermanager.PageUserParams{"1", err.Error(), "account", user.Username, user.Admin, []string{"ayy", "lmao"}})
        return "", err
      }
      return string(hashedPassword), nil
    }(bcrypt.GenerateFromPassword([]byte(password), 15)) // this is the actual hashing call
    if err != nil { // should never happen
      ctx.Render("account_box.html", usermanager.PageUserParams{"1", err.Error(), "account", user.Username, user.Admin, []string{"ayy", "lmao"}})
      return
    }
  }

  if !needQuery { // we don't need to update anything
    ctx.Render("account_box.html", usermanager.PageUserParams{"1", errors.New("nothing to update").Error(), "account", user.Username, user.Admin, []string{"ayy", "lmao"}})
    return
  }

  (*usermanager.Users)[usermanager.SearchUser(userID)].Username = username // update values in runtime users list
  (*usermanager.Users)[usermanager.SearchUser(userID)].Password = hashedPassword

  err = (*usermanager.Users)[usermanager.SearchUser(userID)].Update() // try to update in db
  if err != nil { // failed to update
    ctx.Render("account_box.html", usermanager.PageUserParams{"1", err.Error(), "account", user.Username, user.Admin, []string{"ayy", "lmao"}})
    return
  }

  // TODO success notifications

  if err != nil {
    ctx.Render("account_box.html", usermanager.PageUserParams{"1", err.Error(), "account", user.Username, user.Admin, []string{"ayy", "lmao"}}) // TODO dynamic
  } else {
    ctx.Render("account_box.html", usermanager.PageUserParams{"0", "", "account", user.Username, user.Admin, []string{"ayy", "lmao"}}) // TODO dynamic
  }
}

func adminPostHandler(ctx *iris.Context) {
  _ = usermanager.GenerateTokens(5) // generate tokens and store in db, we don't need them now

  ctx.Redirect("/admin") // just redirect so that we see old+new tokens

  // TODO success notifications

}

func templateHandler(ctx *iris.Context) {
  var params usermanager.PageUserParams
  userID := ctx.GetString("userID")
  user, err := usermanager.GetUser(userID)
  if err != nil {
    if err.Error() != "User not logged in" {
      fmt.Println(err.Error())
    }
  }

  template := ""
  switch ctx.RequestPath(false) {
  default:
    template = "home"
    params = usermanager.PageUserParams{"0", "", template, user.Username, user.Admin, []string{}}
  case "/":
    template = "home"
    params = usermanager.PageUserParams{"0", "", template, user.Username, user.Admin, []string{}}
  case "/account":
    template = "account"
    params = usermanager.PageUserParams{"0", "", template, user.Username, user.Admin, []string{}}
  case "/help":
    template = "help"
    params = usermanager.PageUserParams{"0", "", template, user.Username, user.Admin, []string{}}
  case "/admin":
    template = "admin"
    tokens := usermanager.GetTokens(false)
    params = usermanager.PageUserParams{"0", "", template, user.Username, user.Admin, tokens}
  case "/login":
    template = "login"
    params = usermanager.PageUserParams{"0", "", template, "", "0", []string{"ayy", "lmao"}}
  case "/register":
    template = "register"
    params = usermanager.PageUserParams{"0", "", template, "", "0", []string{}}
  }


  // fmt.Println(ctx.RequestPath(false))

  // fmt.Println(template)

  ctx.MustRender(template + "_box.html", params);

}