package webserver

import (
	"encoding/hex"
	"errors"
	"fmt"
	"html/template"
	"io"
	"log"
	"math/rand"
	"strings"

	gamelangpb "git.alfi.li/gamelang/protobuf/gamelang"
	"github.com/gorilla/sessions"
	"github.com/labstack/echo-contrib/session"
	"github.com/labstack/echo/v4"
	echolog "github.com/labstack/gommon/log"
)

// Template wraps go's html template
type Template struct {
	templates *template.Template
}

// Render renders a Template
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
	return t.templates.ExecuteTemplate(w, name, data)
}

func initTemplates(e *echo.Echo) {
	t := &Template{
		templates: template.Must(template.ParseGlob("public/*.html")),
	}
	e.Renderer = t
}

type Config struct {
	Http struct {
		ListenIP   string
		ListenPort string
	}
}

type Endpoint struct {
	Method   string
	Path     string
	Callback echo.HandlerFunc
}

type Webserver struct {
	config Config
	echo   *echo.Echo

	sessions map[string]gamelangpb.User
}

func NewWebserver(config Config) *Webserver {

	// init echo
	e := echo.New()
	e.Logger.SetLevel(echolog.DEBUG)
	e.Logger.Debug("Debug Log enabled")
	e.Use(session.Middleware(sessions.NewCookieStore([]byte("secret"))))
	initTemplates(e)
	log.Print("echo initialized")

	ws := Webserver{config: config, echo: e, sessions: map[string]gamelangpb.User{}}
	return &ws
}

func (ws *Webserver) NewSession(inputuser gamelangpb.User, ctx echo.Context) string {
	newSessionID, _ := randomHex(32)
	sess, _ := session.Get("GameLang", ctx)
	sess.Values["SessionID"] = newSessionID
	sess.Save(ctx.Request(), ctx.Response())
	user := gamelangpb.User{}
	user.Id = inputuser.Id
	user.Name = inputuser.Name
	ws.sessions[newSessionID] = user
	return newSessionID
}

func (ws *Webserver) CheckSess(ctx echo.Context) (bool, gamelangpb.User) {
	sess, err := session.Get("GameLang", ctx)
	if err != nil {
		log.Print("checkSess: user not logged in")
		return false, gamelangpb.User{}
	}
	sessionID, ok := sess.Values["SessionID"]
	if !ok {
		log.Print("checkSess: no SessionID value")
		return false, gamelangpb.User{}
	}
	user, ok := ws.sessions[sessionID.(string)]
	if !ok {
		log.Println("checkSess: no Session found for ID", sessionID.(string))
		return false, gamelangpb.User{}
	}
	return true, user
}

func (ws *Webserver) ListSess() map[string]gamelangpb.User {
	return ws.sessions
}

func (ws *Webserver) InitEndpoints(endpoints []Endpoint) error {
	for _, endpoint := range endpoints {
		switch strings.ToUpper(endpoint.Method) {
		case "GET":
			ws.echo.GET(endpoint.Path, endpoint.Callback)
		case "POST":
			ws.echo.POST(endpoint.Path, endpoint.Callback)
		default:
			return errors.New("method not implemented")
		}
	}
	return nil
}

func (ws *Webserver) Run() {
	ws.echo.Logger.Fatal(ws.echo.Start(fmt.Sprintf("%v:%v", ws.config.Http.ListenIP, ws.config.Http.ListenPort)))
}

func randomHex(n int) (string, error) {
	bytes := make([]byte, n)
	if _, err := rand.Read(bytes); err != nil {
		return "", err
	}
	return hex.EncodeToString(bytes), nil
}
