package main

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"github.com/gorilla/sessions"
	"github.com/labstack/echo"
	"github.com/labstack/echo-contrib/session"
	"github.com/labstack/echo/middleware"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type userConfig struct {
	Admins []string			`json:"admins"`
	Boards []configBoard	`json:"boards"`
}

func genConfig() *userConfig {
	u := &userConfig{
		make([]string, 0, len(config.Admins)),
		make([]configBoard, 0, len(config.Boards)),
	}
	for k := range config.Admins { u.Admins = append(u.Admins, k) }
	for b := range config.Boards { u.Boards = append(u.Boards, *config.Boards[b]) }
	return u
}

func serveIndex(c echo.Context) error {
	return c.File("server/templates/head.html")
}

func serveInterface(address string) {
	e := echo.New()

	e.Use(middleware.GzipWithConfig(middleware.GzipConfig{Level: 5}))
	e.Use(session.Middleware(sessions.NewCookieStore([]byte(config.Secret))))

	e.GET("/", func(c echo.Context) error {
		sess, _ := session.Get("session", c)
		if auth, ok := sess.Values["authorised"]; ok && auth.(bool) {
			return c.Redirect(http.StatusTemporaryRedirect, "/admin")
		}
		return c.Redirect(http.StatusTemporaryRedirect, "/login")
	})

	e.GET("/login", func(c echo.Context) error {
		sess, _ := session.Get("session", c)
		if auth, ok := sess.Values["authorised"]; ok && auth.(bool) {
			return c.Redirect(http.StatusTemporaryRedirect, "/admin")
		}
		return c.File("server/templates/login.html")
	})
	g := e.Group("/admin")

	g.Use(func(next echo.HandlerFunc) echo.HandlerFunc  {
		return func(c echo.Context) error {
			sess, _ := session.Get("session", c)
			auth, ok := sess.Values["authorised"]
			if !ok || !auth.(bool) {
				return c.Redirect(http.StatusTemporaryRedirect, "/login")
			}
			return next(c)
		}
	})

	g.GET("", serveIndex)
	g.POST("", serveIndex)

	g.POST("/user", func(c echo.Context) error {
		sess, _ := session.Get("session", c)
		name := strings.ToLower(c.FormValue("username"))
		matched, _ := regexp.MatchString(`\w`, name)
		if !matched {
			return c.String(http.StatusNotAcceptable, "Invalid username. Allowed symbols: a-z 0-9 _")
		}
		pass := c.FormValue("password")
		if len(name) == 0 {
			return c.String(http.StatusNotAcceptable, "No username")
		}
		admin, exist := config.Admins[name]
		if exist {
			if len(pass) > 0 {
				if sess.Values["username"] != name {
					return c.String(http.StatusNotAcceptable, "Can't change other user passwords")
				}
				err := admin.newPassword(pass)
				if err != nil {
					fmt.Printf("Failed hash password %s\n", err)
					return c.String(http.StatusNotAcceptable, "Failed to hash")
				}
			}
		} else {
			if len(pass) == 0 {
				return c.String(http.StatusNotAcceptable, "No password")
			}
			admin := configAdmin{name, nil}
			err := admin.newPassword(pass)
			if err != nil {
				fmt.Printf("Failed hash password %s\n", err)
				return c.String(http.StatusNotAcceptable, "Failed to hash")
			}
			config.Admins[name] = &admin
 		}

		saveConfig()
		return c.JSON(http.StatusOK, genConfig())
	})
	g.POST("/board", func(c echo.Context) error {
		id64, err := strconv.ParseUint(c.FormValue("id"), 10, 16)
		if err != nil {
			return c.String(http.StatusNotAcceptable, "Invalid ID")
		}
		id := uint16(id64)
		keysize, err := strconv.ParseUint(c.FormValue("keysize"), 10, 16)
		if err != nil || (keysize != 128 && keysize != 192 && keysize != 256) {
			return c.String(http.StatusNotAcceptable, "Invalid key size")
		}
		keystr := strings.Trim(c.FormValue("key"), " ")
		key, err := hex.DecodeString(keystr)
		if err != nil {
			return c.String(http.StatusNotAcceptable, "Key is not base 16")
		}
		if len(key) != int(keysize/8) && len(key) != 0 {
			return c.String(http.StatusNotAcceptable, "Key does not match key size")
		}

		if board, exist := config.Boards[id]; exist {
			// Update settings
			if len(key) > 0 {
				board.KEY = key
				board.KEY16 = hex.EncodeToString(key)
			}
		} else {
			// New board
			if len(key) == 0 {
				key = make([]byte, keysize/8)
				_, err = rand.Read(key)
				errCheck(err, "Failed generate random key")
			}
			config.Boards[id] = &configBoard{
				id,
				key,
				hex.EncodeToString(key),
				time.Unix(0, 0),
			}
		}
		saveConfig()
		return c.JSON(http.StatusOK, genConfig())
	})
	g.DELETE("/user/:name", func(c echo.Context) error {
		sess, _ := session.Get("session", c)
		if sess.Values["username"] == c.Param("name") {
			return c.String(http.StatusNotAcceptable, "Can't delete yourself")
		}
		_, ok := config.Admins[c.Param("name")]
		if !ok {
			return c.String(http.StatusNotFound, "Not found")
		}

		delete(config.Admins, c.Param("name"))
		saveConfig()
		return c.JSON(http.StatusOK, genConfig())
	})
	g.DELETE("/board/:id", func(c echo.Context) error {
		id64, err := strconv.ParseUint(c.Param("id"), 10, 16)
		if err != nil {
			return c.String(http.StatusNotAcceptable, "Invalid ID")
		}
		id := uint16(id64)
		_, ok := config.Boards[id]
		if !ok {
			return c.String(http.StatusNotFound, "Not found")
		}
		delete(config.Boards, id)
		saveConfig()
		return c.JSON(http.StatusOK, genConfig())
	})

	e.POST("/login", func(c echo.Context) error {
		username := c.FormValue("username")
		password := c.FormValue("password")
		user, ok := config.Admins[username]
		if !ok || !user.checkPassword(password) {
			e.Logger.Printf("User %s failed to login\n", username)
			return c.File("server/templates/login.html")
		}
		e.Logger.Printf("User %s successfully logged in\n", username)
		sess, _ := session.Get("session", c)
		sess.Options = &sessions.Options{
			Path:     "/",
			MaxAge:   86400 * 7,
			HttpOnly: true,
		}
		sess.Values["username"] = user.Username
		sess.Values["authorised"] = true
		err := sess.Save(c.Request(), c.Response())
		errCheck(err, "Failed to save session")
		return c.Redirect(http.StatusTemporaryRedirect, "/admin")
	})

	g.GET("/logout", func(c echo.Context) error {
		sess, _ := session.Get("session", c)
		sess.Values["authorised"] = false
		err := sess.Save(c.Request(), c.Response())
		errCheck(err, "Failed to save session")
		return c.Redirect(http.StatusTemporaryRedirect, "/login")
	})

	g.GET("/logs", func(c echo.Context) error {
		return c.JSON(http.StatusOK, &serverLogs)
	})

	//g.Use(middleware.BasicAuth()
	g.GET("/config", func(c echo.Context) error {
		return c.JSON(http.StatusOK, genConfig())
	})

	e.Logger.Fatal(e.Start(address))

}
