package main

import (
	"log"
	"math/rand"
	"net/http"
	"strings"

	ws "git.alfi.li/gamelang/frontend/webserver"
	gamelangpb "git.alfi.li/gamelang/protobuf/gamelang"
	um "git.alfi.li/gamelang/systems/usermanager"
	wm "git.alfi.li/gamelang/systems/worldmanager"
	"github.com/labstack/echo/v4"

	gcfg "gopkg.in/gcfg.v1"
)

type ExampleConfig struct {
	Http struct {
		Listen string // ip:port
	}
	Kafka struct {
		Enabled            bool
		UserManagerBroker  string
		WorldManagerBroker string
	}
}

func DefaultConfig() ExampleConfig {
	config := ExampleConfig{}
	config.Http.Listen = "0.0.0.0:8080"
	config.Kafka.Enabled = false
	return config
}

type exampleWebServer struct {
	webserver    ws.Webserver
	usermanager  um.UserManager
	worldmanager wm.WorldManager
}

func (ews exampleWebServer) RenderEndpoints() []ws.Endpoint {
	endpoints := []ws.Endpoint{}
	endpoints = append(endpoints, ws.Endpoint{"GET", "/",
		func(ctx echo.Context) error {
			log.Print("/: get")
			ok, user := ews.webserver.CheckSess(ctx)
			if !ok {
				log.Print("/: not found")
				return ctx.Redirect(301, "/login")
			}

			data := struct {
				Username string
				Worlds   []gamelangpb.World
				Items    []string
			}{
				Username: user.Name,
				Worlds:   ews.worldmanager.List(),
				Items: []string{
					"user",
					"world",
				},
			}
			log.Printf("/: user \"%v\" found", user.Name)
			return ctx.Render(http.StatusOK, "index", data)
		}})

	endpoints = append(endpoints, ws.Endpoint{"GET", "/user", func(ctx echo.Context) error {
		log.Print("/user: get")

		log.Print("/user: sending register form")
		return ctx.Render(http.StatusOK, "registerform", "")

	}})

	// POST to /user creates a user
	endpoints = append(endpoints, ws.Endpoint{"POST", "/user", func(ctx echo.Context) error {
		username := ctx.FormValue("username")
		password := ctx.FormValue("password")

		log.Printf("new user request: %v", username)

		user, err := ews.usermanager.Create(rand.Uint32(), username, password)
		if err != nil {
			return ctx.String(501, "user creation failed")
		}

		ews.webserver.NewSession(user, ctx)

		log.Printf("new user created: %v", username)
		return ctx.Redirect(302, "/")
	}})

	endpoints = append(endpoints, ws.Endpoint{"GET", "/login", func(ctx echo.Context) error {
		log.Print("/login: get")
		// check if the login page was called with failed parameter (set by /login POST)
		failedBefore := false
		if value := ctx.FormValue("failed"); value == "1" {
			failedBefore = true
		}

		ok, user := ews.webserver.CheckSess(ctx)
		if ok {
			// logged in
			log.Printf("/login: user %v logged in", user.Name)
			return ctx.String(http.StatusOK, "hello, "+user.Name+"\nyou are logged in.")
		}
		// not logged in
		log.Print("/login: not logged in")
		return ctx.Render(http.StatusOK, "loginform", failedBefore)

	}})

	endpoints = append(endpoints, ws.Endpoint{"POST", "/login", func(ctx echo.Context) error {
		username := ctx.FormValue("username")
		password := ctx.FormValue("password")

		// if the session is already set, the user is already logged in
		ok, _ := ews.webserver.CheckSess(ctx)
		if ok {
			return ctx.String(http.StatusOK, "you are already logged in.")
		}

		log.Printf("user login attempt: %v", username)
		user := gamelangpb.User{}
		user, ok = ews.usermanager.Check(username, password)
		if ok {
			ews.webserver.NewSession(user, ctx)
			log.Printf("user login suceeded: %v", username)
			return ctx.String(http.StatusOK, "hello, "+username+"\nyou are now logged in.")
		}
		log.Printf("user login failed: %v", username)
		return ctx.Redirect(302, "/login?failed=1")
	}})

	endpoints = append(endpoints, ws.Endpoint{"GET", "/admin", func(ctx echo.Context) error {
		log.Print("/admin: get")

		ok, user := ews.webserver.CheckSess(ctx)
		if !ok {
			log.Print("/admin: not logged in")
			return ctx.String(http.StatusUnauthorized, "not logged in")
		}

		log.Println("/admin:", user.Name)
		if user.Admin != true && user.Name != "admin" {
			log.Println("/admin: user is not an admin", user.Name)
			return ctx.String(http.StatusUnauthorized, "you are not an admin")
		}

		data := struct {
			Users    []gamelangpb.User
			Sessions map[string]gamelangpb.User
			Worlds   []gamelangpb.World
		}{
			Users:    ews.usermanager.List(),
			Sessions: ews.webserver.ListSess(),
			Worlds:   ews.worldmanager.List(),
		}

		err := ctx.Render(http.StatusOK, "admin", data)
		if err != nil {
			log.Println(err)
		}
		return err
	}})

	// worldbuilder
	// Get to /World returns register form
	endpoints = append(endpoints, ws.Endpoint{"GET", "/world", func(ctx echo.Context) error {
		ok, _ := ews.webserver.CheckSess(ctx)
		if !ok {
			log.Print("/World: not logged in")
			return ctx.String(http.StatusUnauthorized, "not logged in")
		}
		data := struct {
			Worlds []gamelangpb.World
		}{
			Worlds: ews.worldmanager.List(),
		}
		err := ctx.Render(http.StatusOK, "worldlobby", data)
		if err != nil {
			log.Print(err)
		}
		return err

	}})

	endpoints = append(endpoints, ws.Endpoint{"POST", "/world", func(ctx echo.Context) error {
		ok, _ := ews.webserver.CheckSess(ctx)
		if !ok {
			log.Print("/World: not logged in")
			return ctx.String(http.StatusUnauthorized, "not logged in")
		}
		worldname := ctx.FormValue("worldname")
		for _, world := range ews.worldmanager.List() {
			if worldname == world.Name {
				log.Printf("world \"%s\" already exists", worldname)
				return ctx.String(http.StatusAlreadyReported, "world already exists")
			}
		}

		log.Println("/World: creating world", worldname)
		_, err := ews.worldmanager.Create(rand.Uint32(), worldname)
		if err != nil {
			log.Println("error creating world", err)
			return ctx.String(http.StatusInternalServerError, "error creating world")
		}

		return ctx.Redirect(http.StatusFound, "/world")
	}})

	endpoints = append(endpoints, ws.Endpoint{"POST", "/world/join", func(ctx echo.Context) error {
		log.Print("join attemt")
		ok, user := ews.webserver.CheckSess(ctx)
		if !ok {
			log.Print("/World/join: not logged in")
			return ctx.String(http.StatusUnauthorized, "not logged in")
		}
		worldname := ctx.FormValue("worldname")
		log.Println("joining", user.Name, "to", worldname)
		err := ews.worldmanager.Join(worldname, &user)
		if err != nil {
			log.Printf("error joining %s to %s", user.Name, worldname)
			return ctx.String(501, "join failed")
		}
		log.Println("join of", user.Name, "to", worldname, "complete")
		return ctx.Redirect(302, "/world")
	}})
	return endpoints
}

func main() {
	var config ExampleConfig
	err := gcfg.ReadFileInto(&config, "config")
	if err != nil {
		if strings.Count(err.Error(), "no such file or directory") > 0 {
			config = DefaultConfig()
		} else {
			log.Fatal(err)
		}
	}
	log.Print("config read")
	log.Printf("%+v", config)

	// init usermanager
	brokers := []string{}
	if config.Kafka.Enabled {
		brokers = strings.Split(config.Kafka.UserManagerBroker, ",")
	}
	usermanager := um.NewUserManager(brokers)
	log.Print("usermanager initialized")

	brokers = []string{}
	if config.Kafka.Enabled {
		brokers = strings.Split(config.Kafka.WorldManagerBroker, ",")
	}
	worldmanager := wm.NewWorldManager(brokers)
	log.Print("worldmanager initialized")

	wsConfig := ws.Config{}
	listen := strings.Split(config.Http.Listen, ":")
	wsConfig.Http.ListenIP = listen[0]
	wsConfig.Http.ListenPort = listen[1]
	webserver := ws.NewWebserver(wsConfig)
	log.Print("webserver initialized")

	Ws := exampleWebServer{webserver: *webserver, usermanager: *usermanager, worldmanager: *worldmanager}
	Ws.webserver.InitEndpoints(Ws.RenderEndpoints())
	Ws.webserver.Run()
}
