package controllers

import (
	"bytes"
	"fmt"
	"html/template"
	"net/http"
	"strconv"
	"strings"

	"git.kirsle.net/apps/gophertype/pkg/authentication"
	"git.kirsle.net/apps/gophertype/pkg/markdown"
	"git.kirsle.net/apps/gophertype/pkg/models"
	"git.kirsle.net/apps/gophertype/pkg/responses"
	"git.kirsle.net/apps/gophertype/pkg/session"
	"git.kirsle.net/apps/gophertype/pkg/settings"
	"github.com/albrow/forms"
)

// BlogIndex handles all of the top-level blog index routes:
// - /blog
// - /tagged/{tag}
// - /blog/unlisted
// - /blog/drafts
// - /blog/private
func BlogIndex(privacy string, tagged bool) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var (
			v       = responses.NewTemplateVars(w, r)
			tagName string

			// Multitag values.
			isMultitag bool
			include    []string
			exclude    []string
		)

		// Tagged view?
		if tagged {
			tagName = r.PathValue("tag")
		}

		// Page title to use.
		var title = "Blog"
		if tagged {
			// Check for use of multi-tags.
			if strings.Contains(tagName, ",") {
				title = "Tagged Posts"
				if inc, exc, err := models.ParseMultitag(tagName); err == nil {
					isMultitag = true
					include = inc
					exclude = exc
				}
			} else {
				title = "Tagged as: " + tagName
			}
		} else if privacy == models.Draft {
			title = "Drafts"
		} else if privacy == models.Unlisted {
			title = "Unlisted"
		} else if privacy == models.Private {
			title = "Private"
		}

		v.V["title"] = title
		v.V["tag"] = tagName
		v.V["Multitag"] = struct {
			Is      bool
			Include []string
			Exclude []string
		}{
			isMultitag,
			include,
			exclude,
		}
		v.V["privacy"] = privacy
		responses.RenderTemplate(w, r, "_builtin/blog/index.gohtml", v)
	}
}

// PostFragment at "/<fragment>" for viewing blog entries.
func PostFragment(w http.ResponseWriter, r *http.Request) {
	fragment := strings.Trim(r.URL.Path, "/")
	post, err := models.Posts.LoadFragment(fragment)
	if err != nil {
		responses.NotFound(w, r)
		return
	}

	// Is it a private post and are we logged in?
	if post.Privacy != models.Public && post.Privacy != models.Unlisted && !authentication.LoggedIn(r) {
		responses.Forbidden(w, r, "Permission denied to view that post.")
		return
	}

	v := responses.NewTemplateVars(w, r)
	v.V["post"] = post

	// Render the body.
	v.V["rendered"] = post.HTML()

	responses.RenderTemplate(w, r, "_builtin/blog/view-post.gohtml", v)
}

// PartialBlogIndex is a template function to embed a blog index view on any page.
func PartialBlogIndex(r *http.Request, tag, privacy string) template.HTML {
	html := bytes.NewBuffer([]byte{})
	v := responses.NewTemplateVars(html, r)
	page, _ := strconv.Atoi(r.FormValue("page"))

	var (
		posts models.PagedPosts
		err   error
	)

	if tag != "" {
		posts, err = models.Posts.GetPostsByTag(tag, privacy, page, settings.Current.PostsPerPage)
	} else {
		posts, err = models.Posts.GetIndexPosts(privacy, page, settings.Current.PostsPerPage)
	}

	if err != nil && err.Error() != "sql: no rows in result set" {
		return template.HTML(fmt.Sprintf("[BlogIndex: %s]", err))
	}

	v.V["posts"] = posts.Posts
	v.V["paging"] = posts
	responses.PartialTemplate(html, r, "_builtin/blog/index.partial.gohtml", v)
	return template.HTML(html.String())
}

// TagIndex for "/tagged" to return all tags sorted by popularity.
func TagIndex(w http.ResponseWriter, r *http.Request) {
	// If not logged in, only summarize public post tags.
	var public = !authentication.LoggedIn(r)

	tags := models.SummarizeTags(public)

	v := responses.NewTemplateVars(w, r)
	v.V["tags"] = tags
	responses.RenderTemplate(w, r, "_builtin/blog/tags.gohtml", v)
}

// BlogArchive shows the archive page of ALL blog posts.
func BlogArchive(w http.ResponseWriter, r *http.Request) {
	v := responses.NewTemplateVars(w, r)

	// Show private and unlisted posts?
	showPrivate := authentication.LoggedIn(r)
	archive, err := models.Posts.GetArchive(showPrivate)
	if err != nil {
		responses.Error(w, r, http.StatusInternalServerError, err.Error())
		return
	}

	v.V["archive"] = archive
	responses.RenderTemplate(w, r, "_builtin/blog/archive.gohtml", v)
}

// BlogRandom handles the /blog/random route and picks a random post.
func BlogRandom(w http.ResponseWriter, r *http.Request) {
	post, err := models.Posts.LoadRandom(models.Public)
	if err != nil {
		responses.Error(w, r, http.StatusInternalServerError, err.Error())
		return
	}

	responses.Redirect(w, r, "/"+post.Fragment)
}

// EditPost at "/blog/edit"
func EditPost(w http.ResponseWriter, r *http.Request) {
	v := responses.NewTemplateVars(w, r)
	v.V["preview"] = ""

	// The blog post we're working with.
	var post = models.Posts.New()
	var isNew = true

	// Editing an existing post?
	if r.FormValue("id") != "" {
		id, _ := strconv.Atoi(r.FormValue("id"))
		if p, err := models.Posts.Load(id); err == nil {
			post = p
			isNew = false
		}
	}

	// POST handler: create the admin account.
	for r.Method == http.MethodPost {
		form, _ := forms.Parse(r)

		// Validate form parameters.
		val := form.Validator()
		val.Require("title")
		val.Require("body")
		post.ParseForm(form)
		if val.HasErrors() {
			v.ValidationError = val.ErrorMap()
			break
		}

		// Previewing or submitting the post?
		switch form.Get("submit") {
		case "preview":
			if post.ContentType == models.Markdown {
				v.V["preview"] = template.HTML(markdown.RenderTrustedMarkdown(post.Body))
			} else {
				v.V["preview"] = template.HTML(post.Body)
			}
		case "post":
			author, _ := authentication.CurrentUser(r)
			post.AuthorID = author.ID

			// When editing, allow to not touch the Last Updated time.
			var resetUpdated bool = !isNew && form.GetBool("no-update") == true

			err := post.Save()
			if resetUpdated {
				post.SetUpdated(post.CreatedAt)
			}

			if err != nil {
				v.Error = err
			} else {
				session.Flash(w, r, "Post created!")
				responses.Redirect(w, r, "/"+post.Fragment)
			}
		}

		break
	}

	v.V["post"] = post
	v.V["isNew"] = isNew
	responses.RenderTemplate(w, r, "_builtin/blog/edit.gohtml", v)
}
