package routes

import (
	"crypto/sha256"
	"encoding/base64"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/gorilla/mux"
	"github.com/h2non/filetype"
	"sour.is/x/toolbox/httpsrv"
	"sour.is/x/toolbox/log"

	"sour.is/x/paste/src/pkg/readutil"
)

func init() {
	a := &image{}
	httpsrv.RegisterModule("image", a.config)

	httpsrv.HttpRegister("image", httpsrv.HttpRoutes{
		{Name: "getImage", Method: "GET", Pattern: "/i/{name}", HandlerFunc: a.get},
		{Name: "putImage", Method: "PUT", Pattern: "/i", HandlerFunc: a.put},
		{Name: "getStyle", Method: "GET", Pattern: "/{style:avatar|bg|cover}/", HandlerFunc: a.getStyle},
	})
}

type image struct {
	store   string
	maxSize int64
}

func (a *image) config(config map[string]string) {
	a.store = "data/"
	if config["store"] != "" {
		a.store = config["store"]
	}

	if !chkStore(a.store) {
		log.Criticalf("Image: store location [%s] does not exist or is not writable.", a.store)
	}
	log.Noticef("Image: store location set to [%s]", a.store)

	a.maxSize = 10 * 1024 * 1024
	if s, ok := config["max-size"]; ok {
		if i, err := strconv.Atoi(s); err == nil {
			a.maxSize = int64(i)
		}
	}
	log.Noticef("Image: store max-size set to [%d]", a.maxSize)
}

func (a *image) get(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	name := vars["name"]

	ext := filepath.Ext(name)
	id := strings.TrimSuffix(name, ext)

	fname := filepath.Join(a.store, id)

	if !chkFile(fname) {
		httpsrv.WriteError(w, http.StatusNotFound, "Not Found")

		return
	}

	if chkGone(fname) {
		httpsrv.WriteError(w, http.StatusGone, "Gone")

		return
	}

	f, err := os.Open(fname)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	pr := readutil.NewPreviewReader(f)

	mime, _ := readutil.ReadMIME(pr, name)
	w.Header().Set("Content-Type", mime)
	w.Header().Set("X-Content-Type-Options", "nosniff")

	_, _ = io.Copy(w, pr.Drain())
}

func (a *image) put(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	if h := r.Header.Get("Content-Length"); h != "" {
		log.Debugs("Artifact:", "content-length", h, "max-length", a.maxSize)
		if i, err := strconv.Atoi(h); err == nil {
			if int64(i) > a.maxSize {
				log.Error("ERR Size Too Large")
				http.Error(w, "ERR Size Too Large", http.StatusRequestEntityTooLarge)
				return
			}
		}
	}

	rdr := io.LimitReader(r.Body, a.maxSize)
	pr := readutil.NewPreviewReader(rdr)
	if !isImageOrVideo(pr) {
		httpsrv.WriteError(w, http.StatusUnsupportedMediaType, "ERR Not Image")
		return
	}
	rdr = pr.Drain()

	s256 := sha256.New()
	tmp, err := ioutil.TempFile(a.store, "image-")
	if err != nil {
		httpsrv.WriteError(w, 400, err.Error())
		return
	}
	defer os.Remove(tmp.Name())

	m := io.MultiWriter(s256, tmp)
	if _, err = io.Copy(m, rdr); err != nil {
		httpsrv.WriteError(w, 400, err.Error())
		return
	}
	tmp.Close()

	id := base64.RawURLEncoding.EncodeToString(s256.Sum(nil)[12:])
	fname := filepath.Join(a.store, id)

	log.Debugs("Image: moving file", "src", tmp.Name(), "dst", fname)
	err = os.Rename(tmp.Name(), fname)
	if err != nil {
		httpsrv.WriteError(w, 400, err.Error())
		return
	}

	httpsrv.WriteText(w, http.StatusCreated, id)
}

func isImageOrVideo(in io.Reader) bool {
	buf := make([]byte, 320)
	_, err := in.Read(buf)
	if err != nil {
		return false
	}
	return filetype.IsImage(buf) || filetype.IsVideo(buf)
}

func (a *image) getStyle(w http.ResponseWriter, r *http.Request) {

}
