// Copyright (C) 2019 Marius Schellenberger

package server

import (
	"git.giftfish.de/ston1th/gowiki/pkg/core"
	"git.giftfish.de/ston1th/gowiki/pkg/log"
	"git.giftfish.de/ston1th/gowiki/pkg/otp"
	"git.giftfish.de/ston1th/gowiki/pkg/render"
	"git.giftfish.de/ston1th/jwt/v3"
	"net/http"
	"time"
)

type notFoundHandler struct {
	s *HTTPServer
}

func (nf *notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	newContext(w, r, nf.s).NotFound()
}

func jwtHandler(h ctxHandler) ctxHandler {
	return func(ctx *Context) {
		if c, err := ctx.Request.Cookie(cookieName); err == nil {
			t, err := jwt.DecodeToken(c.Value)
			if err != nil {
				ctx.LogSetCookie("DecodeToken:", err)
				ctx.Redirect(core.IndexURI, http.StatusFound)
				return
			}
			if err = ctx.Srv.JWT.Verify(t); err != nil {
				ctx.LogSetCookie("VerifyToken:", err)
				ctx.Redirect(core.IndexURI, http.StatusFound)
				return
			}
			if t.Claims.GetString(totpClaim) != "" {
				path := ctx.Path()
				if path == core.TotpURI || path == core.LoginURI || path == core.LogoutURI {
					ctx.Token = *t
					h(ctx)
					return
				}
				ctx.Redirect(core.TotpURI, http.StatusFound)
				return

			}
			if !ctx.Srv.DB.LockedOut(t.Claims.GetString(userClaim), t.Claims.GetString(userCreatedClaim)) {
				ctx.Token = *t
				h(ctx)
				return
			}
			if err = ctx.Srv.JWT.Invalidate(t); err != nil {
				log.Println("Invalidate:", err)
			}
		}
		ctx.SetCookie(nil, 0)
		h(ctx)
	}
}

func totpAuthHandler(h ctxHandler) ctxHandler {
	return func(ctx *Context) {
		if ctx.Totp() != "" {
			h(ctx)
			return
		}
		ctx.Redirect(core.IndexURI, http.StatusFound)
	}
}

func adminAuthHandler(h ctxHandler) ctxHandler {
	return func(ctx *Context) {
		if ctx.Admin() {
			h(ctx)
			return
		}
		ctx.Forbidden()
	}
}

func userAuthHandler(h ctxHandler) ctxHandler {
	return func(ctx *Context) {
		if ctx.Admin() {
			h(ctx)
			return
		}
		if ctx.User() == ctx.Var("user") {
			h(ctx)
			return
		}
		ctx.Forbidden()
	}
}

func authHandler(h ctxHandler) ctxHandler {
	return func(ctx *Context) {
		if ctx.LoggedOn() {
			h(ctx)
			return
		}
		ctx.Forbidden()
	}
}

func staticHandler(ctx *Context) {
	var err error
	ctx.SetHeader("Content-Type", "text/css; charset=utf-8")
	ctx.SetHeader("Expires", time.Now().UTC().Add(max).Format(http.TimeFormat))
	switch ctx.Path() {
	case core.BootstrapCSS:
		err = ctx.Write(ctx.Srv.res["bootstrap.css"])
	case core.CustomCSS:
		err = ctx.Write(ctx.Srv.res["custom.css"])
	case core.Favicon:
		ctx.SetHeader("Content-Type", "image/x-icon")
		err = ctx.Write(ctx.Srv.res["favicon.ico"])
	}
	if err != nil {
		log.Println("static:", err)
	}
}

func indexHandler(ctx *Context) {
	ctx.Redirect(core.IndexURI, http.StatusFound)
}

func loginHandler(ctx *Context) {
	if ctx.LoggedOn() {
		ctx.Redirect(core.IndexURI, http.StatusFound)
		return
	}
	ctx.Template("loginHandler")
	ctx.Data = webData{
		Title:     "Login",
		BodyTitle: "Login",
	}
	var data loginData
	switch ctx.Method() {
	case "GET":
		data.Referer = ctx.RefererURI()
		ctx.Data.Data = data
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}
		data.User = ctx.Form("user")
		data.Referer = ctx.Form("referer")
		ctx.Data.Data = data
		if data.Referer == "" {
			data.Referer = core.IndexURI
		}
		password := ctx.Form("password")
		if data.User == "" || password == "" {
			ctx.Error("empty username or password")
			return
		}
		u, err := ctx.Srv.DB.Login(data.User, password)
		if err != nil {
			log.Printf("login: %s from: %s %s", data.User, ctx.Forwarded(), err)
			ctx.Error("bad username or password")
			return
		}
		if u.Secret == "" {
			log.Println("login:", u.Username)
			ctx.Login(u, ctx.Form("remember"))
			ctx.Redirect(data.Referer, http.StatusFound)
			return
		}
		ctx.SetCookie(jwt.Claims{
			totpClaim:     u.Username,
			rememberClaim: ctx.Form("remember"),
			refererClaim:  data.Referer,
		}, time.Minute)
		ctx.Redirect(core.TotpURI, http.StatusFound)
	}
}

func loginTotpHandler(ctx *Context) {
	if ctx.LoggedOn() {
		ctx.Redirect(core.IndexURI, http.StatusFound)
		return
	}
	ctx.Template("loginTotpHandler")
	ctx.Data = webData{
		Title:     "TOTP Verification",
		BodyTitle: "TOTP Veriftcation",
	}
	switch ctx.Method() {
	case "GET":
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}
		pin := ctx.Form("pin")
		if pin == "" {
			ctx.Error("empty pin")
			return
		}
		user := ctx.Totp()
		u, err := ctx.Srv.DB.Totp(user, pin)
		if err != nil {
			log.Printf("totp: %s from: %s %s", user, ctx.Forwarded(), err)
			ctx.Error(err)
			return
		}
		ref := ctx.Referer()
		log.Println("login:", u.Username)
		ctx.Login(u, ctx.Remember())
		ctx.Redirect(ref, http.StatusFound)
	}
}

func searchHandler(ctx *Context) {
	ctx.Template("searchHandler")
	ctx.Data = webData{
		Title:     "Search",
		BodyTitle: "Search",
	}
	switch ctx.Method() {
	case "GET":
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}
		search := ctx.Form("search")

		res, err := ctx.Srv.DB.Index.Search(search)
		if err != nil {
			ctx.Error(err)
			return
		}
		user := ctx.User()
		r := make([]core.Result, len(res))
		i := 0
		for _, v := range res {
			if ctx.Srv.DB.ValidatePage(v.StoreTitle, user) {
				r[i] = v
				i++
			}
		}

		ctx.Data.Data = r[:i]
		ctx.Data.Search = search
		ctx.Exec()
	}
}

func sectionsHandler(ctx *Context) {
	ctx.Template("sectionsHandler")
	ctx.Data = webData{
		Title:     "All Sections",
		BodyTitle: "All Sections",
		Admin:     ctx.Admin(),
	}
	ctx.Data.Data = ctx.Srv.DB.GetAllSections(ctx.User(), ctx.Admin())
	ctx.Exec()
}

func sectionHandler(ctx *Context) {
	ctx.Template("sectionHandler")
	section := ctx.Var("section")
	ctx.Data = webData{
		Title:     section,
		BodyTitle: section,
		Admin:     ctx.Admin(),
	}
	var err error
	ctx.Data.Data, err = ctx.Srv.DB.GetAllSectionPages(section, ctx.User(), ctx.Admin())
	if err != nil {
		ctx.NotFound()
		return
	}
	ctx.Exec()
}

func sectionNewHandler(ctx *Context) {
	ctx.Template("sectionNewHandler")
	ctx.Data = webData{
		Title:     "New Section",
		BodyTitle: "New Section",
	}
	users, err := ctx.Srv.DB.GetUsers()
	if err != nil {
		ctx.Error(err)
		return
	}
	sd := sectionData{Members: make(map[string]bool)}
	for _, u := range users {
		sd.Members[u.Username] = false
	}
	switch ctx.Method() {
	case "GET":
		ctx.Data.Data = sd
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}
		section := ctx.Form("section")
		members := ctx.FormSlice("members")
		for _, m := range members {
			sd.Members[m] = true
		}
		ctx.Data.Data = sd
		if section == "" {
			ctx.Error("empty section")
			return
		}
		err = ctx.Srv.DB.CreateSection(section, members, false)
		if err != nil {
			ctx.Error(err)
			return
		}
		log.Printf("create: section %s created by %s\n", section, ctx.User())
		ctx.Redirect(core.SectionsURI, http.StatusFound)
	}
}

func sectionEditHandler(ctx *Context) {
	ctx.Template("sectionEditHandler")
	ctx.Data = webData{
		Title:     "Edit Section",
		BodyTitle: "Edit Section",
	}
	users, err := ctx.Srv.DB.GetUsers()
	if err != nil {
		ctx.Error(err)
		return
	}
	sd := sectionData{Members: make(map[string]bool)}
	for _, u := range users {
		sd.Members[u.Username] = false
	}
	s, err := ctx.Srv.DB.GetSection(ctx.Var("section"))
	if err != nil {
		ctx.Error(err)
		return
	}
	sd.Name = s.Name
	switch ctx.Method() {
	case "GET":
		for _, m := range s.Members {
			sd.Members[m] = true
		}
		ctx.Data.Data = sd
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}
		members := ctx.FormSlice("members")
		for _, m := range members {
			sd.Members[m] = true
		}
		ctx.Data.Data = sd
		err = ctx.Srv.DB.UpdateSection(s.Name, members, s.User)
		if err != nil {
			ctx.Error(err)
			return
		}
		log.Printf("update: section %s updated by %s\n", s.Name, ctx.User())
		ctx.Redirect(core.SectionsURI, http.StatusFound)
	}
}

func sectionDelHandler(ctx *Context) {
	ctx.Template("sectionDelHandler")
	ctx.Data = webData{
		Title:     "Delete Section",
		BodyTitle: "Delete Section",
	}
	s, err := ctx.Srv.DB.GetSection(ctx.Var("section"))
	if err != nil {
		ctx.Error(err)
		return
	}
	if s.User {
		ctx.Error("user section can not be deleted")
		return
	}
	switch ctx.Method() {
	case "GET":
		ctx.Data.Data = s.Name
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}
		err := ctx.Srv.DB.DeleteSection(s.Name)
		if err != nil {
			ctx.Error(err)
			return
		}
		log.Printf("delete: section %s deleted by %s\n", s.Name, ctx.User())
		ctx.Redirect(core.SectionsURI, http.StatusFound)
	}
}

func pageNewHandler(ctx *Context) {
	ctx.Template("pageNewHandler")
	ctx.Data = webData{Title: "New Page"}
	user := ctx.User()
	secs, err := ctx.Srv.DB.GetUserSections(user)
	if err != nil {
		ctx.Error(err)
		return
	}
	switch ctx.Method() {
	case "GET":
		secs[user] = true
		ctx.Data.Data = pageData{core.Page{Perm: core.Public}, secs}
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}
		section := ctx.Form("section")
		title := ctx.Form("title")
		markdown := ctx.Form("markdown")
		perm := core.ParsePermString(ctx.Form("perm"))
		p := core.Page{
			Title:    title,
			Markdown: markdown,
			Perm:     perm,
		}
		secs[section] = true
		ctx.Data.Data = pageData{p, secs}
		if title == "" {
			ctx.Error("empty title")
			return
		}
		if perm == core.Invalid {
			ctx.Error("invalid permission")
			return
		}

		page, err := ctx.Srv.DB.CreatePage(title, section, markdown, user, perm)
		if err != nil {
			ctx.Error(err)
			return
		}
		ctx.Redirect(page.StoreTitle, http.StatusFound)
	}
}

func pageHandler(ctx *Context) {
	var pageCreated string
	ctx.Data = webData{
		Admin: ctx.Admin(),
	}
	section := ctx.Var("section")
	title := ctx.Var("title")
	user := ctx.User()
	key := ctx.Var("key")
	if key != "" {
		t, err := jwt.DecodeToken(key)
		if err != nil {
			log.Println("share: DecodeToken:", err)
			ctx.NotFound()
			return
		}
		if err = ctx.Srv.JWT.Verify(t); err != nil {
			log.Println("share: VerifyToken:", err)
			ctx.NotFound()
			return
		}
		user = t.Claims.GetString(sharedClaim)
		if ctx.Srv.DB.LockedOut(user, t.Claims.GetString(userCreatedClaim)) {
			if err = ctx.Srv.JWT.Invalidate(t); err != nil {
				log.Println("share: Invalidate:", err)
			}
			ctx.NotFound()
			return
		}
		section = t.Claims.GetString(sectionClaim)
		title = t.Claims.GetString(titleClaim)
		pageCreated = t.Claims.GetString(pageCreatedClaim)
		ctx.Data.Key = key
		if ctx.T == nil {
			ctx.Template("pageViewHandler")
		}
	} else {
		if ctx.T == nil {
			ctx.Template("pageHandler")
		}
	}
	page, err := ctx.Srv.DB.GetPage(section, title, user)
	if err != nil {
		log.Println(err)
		ctx.NotFound()
		return
	}
	if key != "" && page.Created != pageCreated {
		ctx.NotFound()
		return
	}
	ctx.Data.Data = page
	ctx.Data.Title = section + " | " + page.Title
	ctx.Data.BodyTitle = section
	ctx.Exec()
}

func pageMDHandler(f ctxHandler) ctxHandler {
	return func(ctx *Context) {
		ctx.Template("pageMDHandler")
		f(ctx)
	}
}

func pageShareHandler(ctx *Context) {
	ctx.Template("pageShareHandler")
	ctx.Data = webData{}
	section := ctx.Var("section")
	title := ctx.Var("title")
	user := ctx.User()
	page, err := ctx.Srv.DB.GetPage(section, title, user)
	if err != nil {
		ctx.NotFound()
		return
	}
	if page.Perm != core.Private {
		ctx.Error("only private pages can be shared")
		return
	}
	ctx.Data.Data = page.StoreTitle
	ctx.Data.Title = page.Title
	ctx.Data.BodyTitle = page.Title
	switch ctx.Method() {
	case "GET":
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}

		d, err := duration(ctx.Form("duration"), ctx.Form("mode"))
		if err != nil {
			ctx.Error(err)
			return
		}
		t := jwt.NewToken(map[string]interface{}{
			sharedClaim:      user,
			userCreatedClaim: ctx.Token.Claims.GetString(userCreatedClaim),
			pageCreatedClaim: page.Created,
			sectionClaim:     section,
			titleClaim:       title,
			jwt.ExpClaim:     jwt.NewExp(d),
		}, nil)

		if err = ctx.Srv.JWT.Sign(t); err != nil {
			log.Println("share: SignToken:", err)
			ctx.NotFound()
			return
		}
		ctx.Data.Key = t.String()
		ctx.Exec()
	}
}

func pageBlacklistHandler(ctx *Context) {
	ctx.Template("pageBlacklistHandler")
	ctx.Data = webData{
		Title:     "Blacklist",
		BodyTitle: "Blacklist",
	}
	switch ctx.Method() {
	case "GET":
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}

		t, err := jwt.DecodeToken(ctx.Form("share"))
		if err != nil {
			ctx.Error(err)
			return
		}
		err = ctx.Srv.JWT.Verify(t)
		if err != nil {
			ctx.Error(err)
			return
		}
		user := t.Claims.GetString(sharedClaim)
		if user == "" {
			ctx.Error("blacklisting denied: no share token")
			return
		}
		if ctx.User() != user {
			ctx.Error("blacklisting denied: token was issued by " + user)
			return
		}
		err = ctx.Srv.JWT.Invalidate(t)
		if err != nil {
			ctx.Error(err)
			return
		}
		ctx.Exec()
	}
}

func pageEditHandler(ctx *Context) {
	ctx.Template("pageEditHandler")
	ctx.Data = webData{}
	section := ctx.Var("section")
	title := ctx.Var("title")
	page, err := ctx.Srv.DB.GetPage(section, title, ctx.User())
	if err != nil {
		ctx.NotFound()
		return
	}
	ctx.Data.Title = page.Title
	ctx.Data.BodyTitle = page.Title
	switch ctx.Method() {
	case "GET":
		ctx.Data.Data = page
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}
		page.Markdown = ctx.Form("markdown")
		page.Perm = core.ParsePermString(ctx.Form("perm"))
		ctx.Data.Data = page

		err = ctx.Srv.DB.UpdatePage(title, section, page.Markdown, ctx.User(), page.Perm)
		if err != nil {
			ctx.Error(err)
			return
		}
		ctx.Redirect(page.StoreTitle, http.StatusFound)
	}
}

func pageDelHandler(ctx *Context) {
	section := ctx.Var("section")
	title := ctx.Var("title")
	ctx.Template("pageDelHandler")
	ctx.Data.Data = render.StoreTitle(section, title)
	ctx.Data.Title = title
	ctx.Data.BodyTitle = title
	switch ctx.Method() {
	case "GET":
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}
		if err := ctx.Srv.DB.DeletePage(title, section, ctx.User()); err != nil {
			ctx.Error(err)
			return
		}
		ctx.Redirect(core.IndexURI, http.StatusFound)
	}
}

func usersHandler(ctx *Context) {
	if !ctx.LoggedOn() {
		ctx.NotFound()
		return
	}
	ctx.Template("usersHandler")
	ctx.Data = webData{
		Title:     "Users",
		BodyTitle: "Users",
		User:      ctx.User(),
	}
	us, err := ctx.Srv.DB.GetUsers()
	if err != nil {
		ctx.Data.Msg = err.Error()
	}
	ctx.Data.Data = us
	ctx.Exec()
}

func userNewHandler(ctx *Context) {
	ctx.Template("userNewHandler")
	ctx.Data = webData{
		Title:     "New User",
		BodyTitle: "New User",
	}
	switch ctx.Method() {
	case "GET":
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}
		user := ctx.Form("user")
		password := ctx.Form("password")
		repeat := ctx.Form("repeat")
		admin := ctx.Form("admin")
		adm := false
		if admin == "0" {
			adm = true
		}

		if user == "" || password == "" {
			ctx.Error("empty user or password")
			return
		}
		if password != repeat {
			ctx.Error("passwords do not match")
			return
		}
		if err := ctx.Srv.DB.CreateUser(user, password, adm); err != nil {
			ctx.Error(err)
			return
		}
		log.Printf("create: user %s created by %s\n", user, ctx.User())
		ctx.Redirect("/users", http.StatusFound)
	}
}

func userEditHandler(ctx *Context) {
	ctx.Template("userEditHandler")
	ctx.Data = webData{
		Title:     "Edit User",
		BodyTitle: "Edit User",
	}
	u, err := ctx.Srv.DB.GetUserWithoutPassword(ctx.Var("user"))
	if err != nil {
		ctx.Error(err)
		return
	}
	ctx.Data.Data = u
	switch ctx.Method() {
	case "GET":
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}
		user := ctx.Var("user")
		password := ctx.Form("password")
		repeat := ctx.Form("repeat")
		admin := ctx.Form("admin")
		adm := false
		if admin == "0" {
			adm = true
		}

		if password != repeat {
			ctx.Error("passwords do not match")
			return
		}
		if ctx.Admin() {
			if err := ctx.Srv.DB.AdminUpdateUser(user, password, adm); err != nil {
				ctx.Error(err)
				return
			}
			log.Printf("update: user %s updated by %s\n", user, ctx.User())
			ctx.Redirect("/users", http.StatusFound)
			return
		}
		if err := ctx.Srv.DB.UpdateUserPassword(user, password); err != nil {
			ctx.Error(err)
			return
		}
		log.Printf("update: %s updated by %s\n", user, ctx.User())
		ctx.Redirect(core.IndexURI, http.StatusFound)
	}
}

func userTotpHandler(ctx *Context) {
	ctx.Template("userTotpHandler")
	ctx.Data = webData{
		Title:     "TOTP",
		BodyTitle: "TOTP",
	}
	req := otp.Request{Username: ctx.Var("user")}
	u, err := ctx.Srv.DB.GetUser(req.Username)
	if err != nil {
		ctx.Error(err)
		return
	}
	if u.Secret == "" {
		switch ctx.Method() {
		case "GET":
			req, err = otp.New(req.Username)
			if err != nil {
				ctx.Error(err)
				return
			}
		case "POST":
			req.URL = ctx.Form("url")
			req.Image, req.Secret, err = otp.ImageSecretFromURL(req.URL)
			if err != nil {
				ctx.Error(err)
				return
			}
		}
	}
	switch ctx.Method() {
	case "GET":
		ctx.Data.Data = req
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}
		pin := ctx.Form("pin")
		if pin == "" {
			ctx.Error("empty pin")
			return
		}
		ctx.Data.Data = req
		secret := req.Secret
		if u.Secret != "" {
			secret = u.Secret
		}
		valid := otp.Validate(pin, secret)
		user := ctx.User()
		if user != req.Username && ctx.Admin() {
			valid = true
		}

		if !valid {
			ctx.Error("validation failed")
			return
		}
		if err := ctx.Srv.DB.UpdateUserSecret(req.Username, req.Secret); err != nil {
			ctx.Error(err)
			return
		}
		if req.Secret == "" {
			log.Printf("totp: disabled by %s for %s\n", user, req.Username)
		} else {
			log.Printf("totp: enabled by %s for %s\n", user, req.Username)
		}
		ctx.Redirect("/user/edit/"+req.Username, http.StatusFound)
	}
}

func userUnlockHandler(ctx *Context) {
	user := ctx.Var("user")
	if err := ctx.Srv.DB.UnlockUser(user); err != nil {
		log.Println("unlock:", err)
	}
	log.Printf("unlock: %s unlocked by %s\n", user, ctx.User())
	ctx.Redirect("/users", http.StatusFound)
}

func userDelHandler(ctx *Context) {
	ctx.Template("userDelHandler")
	ctx.Data = webData{
		Title:     "Delete User",
		BodyTitle: "Delete User",
	}
	switch ctx.Method() {
	case "GET":
		user, err := ctx.Srv.DB.GetUserWithoutPassword(ctx.Var("user"))
		if err != nil {
			ctx.Error(err)
			return
		}
		ctx.Data.Data = user
		ctx.Exec()
	case "POST":
		if !ctx.CheckXsrf() {
			return
		}
		self := ctx.User()
		user := ctx.Var("user")
		if err := ctx.Srv.DB.DeleteUser(user); err != nil {
			log.Println("delete:", err)
		}
		log.Printf("delete: user %s deleted by %s\n", user, self)
		if user == self {
			ctx.Redirect("/logout", http.StatusFound)
			return
		}
		ctx.Redirect("/users", http.StatusFound)
	}
}

func logoutHandler(ctx *Context) {
	var totp string
	user := ctx.User()
	if user == "" {
		user = ctx.Totp()
		if user != "" {
			totp = " (totp)"
		}
	}
	if user != "" {
		ctx.Srv.JWT.Invalidate(&ctx.Token)
	} else {
		user = empty
	}
	log.Printf("logout: %s%s", user, totp)
	ctx.SetCookie(nil, 0)
	ctx.Redirect(core.IndexURI, http.StatusFound)
}
