package routes

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

	"github.com/gorilla/mux"
	"golang.org/x/sys/unix"
	"sour.is/x/paste/src/pkg/readutil"
	"sour.is/x/toolbox/httpsrv"
	"sour.is/x/toolbox/log"
)

func init() {
	p := &Paste{}

	httpsrv.RegisterModule("paste", p.setConfig)

	httpsrv.HttpRegister("paste", httpsrv.HttpRoutes{
		{Name: "getRandom", Method: "GET", Pattern: "/paste/rng", HandlerFunc: p.getRandom},
		{Name: "getPaste", Method: "GET", Pattern: "/paste/{id}", HandlerFunc: p.getPaste},
		{Name: "getPaste", Method: "GET", Pattern: "/paste/get/{id}", HandlerFunc: p.getPaste},
		{Name: "postPaste", Method: "POST", Pattern: "/paste", HandlerFunc: p.postPaste},

		{Name: "getRandom", Method: "GET", Pattern: "/api/rng", HandlerFunc: p.getRandom},
		{Name: "getPaste", Method: "GET", Pattern: "/api/{id}", HandlerFunc: p.getPaste},
		{Name: "getPaste", Method: "GET", Pattern: "/api/get/{id}", HandlerFunc: p.getPaste},
		{Name: "postPaste", Method: "POST", Pattern: "/api", HandlerFunc: p.postPaste},
	})
}

type Paste struct {
	store     string
	randBytes int
}

func (p *Paste) setConfig(config map[string]string) {
	p.store = "data/"
	if config["store"] != "" {
		p.store = config["store"]
	}

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

	p.randBytes = 1024
	if config["random"] != "" {
		p.randBytes, _ = strconv.Atoi(config["random"])
		log.Noticef("Paste: set random size to %d bytes", p.randBytes)
	}
}

func chkStore(path string) bool {
	file, err := os.Stat(path)

	if err != nil && os.IsNotExist(err) {
		err = os.MkdirAll(path, 0744)
		if err != nil {
			return false
		}

		file, err = os.Stat(path)
	}

	if err != nil {
		return false
	}

	if !file.IsDir() {
		return false
	}

	if unix.Access(path, unix.W_OK&unix.R_OK) != nil {
		return false
	}

	return true
}

func chkFile(path string) bool {
	file, err := os.Stat(path)
	if err != nil {
		return false
	}

	if file.IsDir() {
		return false
	}

	if unix.Access(path, unix.W_OK&unix.R_OK) != nil {
		return false
	}

	return true
}

func chkGone(path string) bool {
	file, err := os.Stat(path)
	if err != nil {
		return true
	}

	if file.Size() == 0 {
		return true
	}

	return false
}

func (p *Paste) getRandom(w http.ResponseWriter, _ *http.Request) {
	s := make([]byte, p.randBytes)
	_, _ = rand.Read(s)

	w.Header().Set("content-type", "application/octet-stream")
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(s)
}

func (p *Paste) getPaste(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id := vars["id"]

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

	if !chkFile(fname) {
		httpsrv.WriteText(w, http.StatusNotFound, "ERR Not Found")
		return
	}

	if chkGone(fname) {
		httpsrv.WriteText(w, http.StatusGone, "ERR Gone")
		return
	}

	f, err := os.Open(fname)
	if err != nil {
		log.Fatal(err)
		httpsrv.WriteText(w, http.StatusInternalServerError, "ERR Reading Content")
	}
	defer f.Close()

	pr := readutil.NewPreviewReader(f)

	keep := true
	scanner := bufio.NewScanner(pr)
	for scanner.Scan() {
		txt := scanner.Text()
		log.Debug(txt)

		// End of header found
		if txt == "" {
			break
		}

		if strings.HasPrefix(txt, "exp:") {
			now := time.Now().Unix()
			exp, err := strconv.ParseInt(strings.TrimSpace(strings.TrimPrefix(txt, "exp:")), 10, 64)
			if err != nil {
				log.Warning(err)
			}

			log.Debugf("%d > %d", now, exp)
			if now > exp {
				w.WriteHeader(http.StatusGone)
				_, _ = w.Write([]byte("ERR Gone"))

				deleteFile(fname)

				return
			}
		}

		if strings.HasPrefix(txt, "burn:") {
			burn := strings.TrimSpace(strings.TrimPrefix(txt, "burn:"))

			if burn == "true" {
				keep = false
			}
		}
	}

	w.WriteHeader(http.StatusOK)
	_, _ = io.Copy(w, pr.Drain())

	if !keep {
		deleteFile(fname)
	}
}

func (p *Paste) postPaste(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	rdr := io.LimitReader(r.Body, 1048576)

	s256 := sha256.New()
	tmp, _ := ioutil.TempFile(p.store, "arch-")
	defer os.Remove(tmp.Name())

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

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

	_ = os.Rename(tmp.Name(), fname)

	httpsrv.WriteText(w, http.StatusCreated, "OK "+id)
}

// func checkErr(err error, w http.ResponseWriter) bool {
// 	if err != nil {
// 		httpsrv.WriteObject(w, http.StatusBadRequest, err)
// 		return true
// 	}

// 	return false
// }

func deleteFile(path string) {
	_ = ioutil.WriteFile(path, nil, 0644)
}
