package photo

import (
	"bytes"
	"io"
	"net/http"
	"os"
	"path/filepath"

	"git.kirsle.net/apps/gosocial/pkg/log"
	"git.kirsle.net/apps/gosocial/pkg/models"
	"git.kirsle.net/apps/gosocial/pkg/photo"
	"git.kirsle.net/apps/gosocial/pkg/session"
	"git.kirsle.net/apps/gosocial/pkg/templates"
)

// Upload photos controller.
func Upload() http.HandlerFunc {
	tmpl := templates.Must("photo/upload.html")
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var vars = map[string]interface{}{
			"Intent":    r.FormValue("intent"),
			"NeedsCrop": false,
		}

		// Query string parameters: what is the intent of this photo upload?
		// - If profile picture, the user will crop their image before posting it.
		// - If regular photo, user simply picks a picture and doesn't need to crop it.
		if vars["Intent"] == "profile_pic" {
			vars["NeedsCrop"] = true
		}

		user, err := session.CurrentUser(r)
		if err != nil {
			session.FlashError(w, r, "Unexpected error: couldn't get CurrentUser")
		}

		// Get the current user's quota.
		var photoCount, photoQuota = photo.QuotaForUser(user)
		vars["PhotoCount"] = photoCount
		vars["PhotoQuota"] = photoQuota

		// Are they POSTing?
		if r.Method == http.MethodPost {
			var (
				caption    = r.PostFormValue("caption")
				isExplicit = r.PostFormValue("explicit") == "true"
				visibility = r.PostFormValue("visibility")
				isGallery  = r.PostFormValue("gallery") == "true"
				cropCoords = r.PostFormValue("crop")
				confirm1   = r.PostFormValue("confirm1") == "true"
				confirm2   = r.PostFormValue("confirm2") == "true"
			)

			// Are they at quota already?
			if photoCount >= photoQuota {
				session.FlashError(w, r, "You have too many photos to upload a new one. Please delete a photo to make room for a new one.")
				templates.Redirect(w, "/photo/u/"+user.Username)
				return
			}

			// They checked both boxes. The browser shouldn't allow them to
			// post but validate it here anyway...
			if !confirm1 || !confirm2 {
				session.FlashError(w, r, "You must agree to the terms to upload this picture.")
				templates.Redirect(w, r.URL.Path)
				return
			}

			// Parse and validate crop coordinates.
			var crop []int
			if vars["NeedsCrop"] == true {
				crop = photo.ParseCropCoords(cropCoords)
			}

			log.Error("parsed crop coords: %+v", crop)

			// Get their file upload.
			file, header, err := r.FormFile("file")
			if err != nil {
				session.FlashError(w, r, "Error receiving your file: %s", err)
				templates.Redirect(w, r.URL.Path)
				return
			}

			// Read the file contents.
			log.Debug("Receiving uploaded file (%d bytes): %s", header.Size, header.Filename)
			var buf bytes.Buffer
			io.Copy(&buf, file)

			filename, cropFilename, err := photo.UploadPhoto(photo.UploadConfig{
				User:      user,
				Extension: filepath.Ext(header.Filename),
				Data:      buf.Bytes(),
				Crop:      crop,
			})
			if err != nil {
				session.FlashError(w, r, "Error in UploadPhoto: %s", err)
				templates.Redirect(w, r.URL.Path)
				return
			}

			// Configuration for the DB entry.
			ptmpl := models.Photo{
				UserID:          user.ID,
				Filename:        filename,
				CroppedFilename: cropFilename,
				Caption:         caption,
				Visibility:      models.PhotoVisibility(visibility),
				Gallery:         isGallery,
				Explicit:        isExplicit,
			}

			// Get the filesize.
			if stat, err := os.Stat(photo.DiskPath(filename)); err == nil {
				ptmpl.Filesize = stat.Size()
			}

			// Create it in DB!
			p, err := models.CreatePhoto(ptmpl)
			if err != nil {
				session.FlashError(w, r, "Couldn't create Photo in DB: %s", err)
			} else {
				log.Info("New photo! %+v", p)
			}

			// Are we uploading a profile pic? If so, set the user's pic now.
			if vars["Intent"] == "profile_pic" {
				log.Info("User %s is setting their profile picture", user.Username)
				user.ProfilePhoto = *p
				user.Save()
			}

			session.Flash(w, r, "Your photo has been uploaded successfully.")
			templates.Redirect(w, "/photo/u/"+user.Username)
			return
		}

		if err := tmpl.Execute(w, r, vars); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	})
}
