package routes

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

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

var store string
var randBytes int

func init() {
	httpsrv.RegisterModule("paste", setConfig)

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

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

func setConfig(config map[string]string) {

	store = "data/"
	if config["store"] != "" {
		store = config["store"]
	}

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

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

func chkStore(path string) bool {
	file, err := os.Stat(path)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		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 true
	}
	if os.IsNotExist(err) {
		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 getRandom(w http.ResponseWriter, r *http.Request) {
	s := make([]byte, randBytes)
	rand.Read(s)

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

func getPaste(w http.ResponseWriter, r *http.Request) {

	vars := mux.Vars(r)
	id := vars["id"]

	if !chkFile(store + id) {
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte("ERR Not Found"))
		return
	}

	if chkGone(store + id) {
		w.WriteHeader(http.StatusGone)
		w.Write([]byte("ERR Gone"))
		return
	}

	head, err := os.Open(store + id)
	if err != nil {
		log.Fatal(err)
	}
	defer head.Close()

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

		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(store + id)

				return
			}
		}

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

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

	file, _ := os.Open(store + id)
	defer file.Close()

	w.WriteHeader(http.StatusOK)

	scanner = bufio.NewScanner(file)
	for scanner.Scan() {
		w.Write(scanner.Bytes())
		w.Write([]byte("\n"))
	}

	if !keep {
		deleteFile(store + id)
	}
}

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

	body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
	checkErr(err, w)

	s256 := sha256.Sum256(body)
	id := base64.RawURLEncoding.EncodeToString(s256[12:])

	ioutil.WriteFile(store+id, body, 0644)

	w.WriteHeader(http.StatusCreated)
	w.Write([]byte("OK " + id))

	w.WriteHeader(http.StatusCreated)
}

func checkErr(err error, w http.ResponseWriter) {
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		json.NewEncoder(w).Encode(err)
		panic(err)
	}
}

func deleteFile(path string) {
	ioutil.WriteFile(path, []byte(""), 0644)
}
