package routes

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

	"github.com/gorilla/mux"
	"sour.is/x/paste/src/pkg/readutil"
	"sour.is/x/toolbox/httpsrv"
	"sour.is/x/toolbox/log"

	"github.com/gomarkdown/markdown"
	"github.com/gomarkdown/markdown/html"
	"github.com/gomarkdown/markdown/parser"
)

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

	httpsrv.HttpRegister("artifact", httpsrv.HttpRoutes{
		{Name: "get-path", Method: "GET", Pattern: "/a/{name}/{path:.*}", HandlerFunc: a.get},
		{Name: "get", Method: "GET", Pattern: "/a/{name}", HandlerFunc: a.get},
		{Name: "put", Method: "PUT", Pattern: "/a", HandlerFunc: a.put},
		{Name: "get", Method: "GET", Pattern: "/a", HandlerFunc: a.list},
	})
}

// Artifact stores items to disk
type Artifact struct {
	store   string
	maxSize int64
}

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

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

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

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

	name := vars["name"]
	path, hasPath := vars["path"]

	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.Error(err)
	}
	defer f.Close()

	if !hasPath {
		pr := readutil.NewPreviewReader(f)

		mime, err := readutil.ReadMIME(pr, name)
		if err != nil {
			httpsrv.WriteError(w, http.StatusInternalServerError, err.Error())
			return
		}
		w.Header().Set("Content-Type", mime)
		w.Header().Set("X-Content-Type-Options", "nosniff")

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

	rdr, err := readutil.Decompress(f)
	if err != nil && err != readutil.ErrUnsupportedType {
		httpsrv.WriteError(w, http.StatusInternalServerError, err.Error())
		return
	}
	if cl, ok := rdr.(io.ReadCloser); ok {
		defer cl.Close()
	}

	tr := tar.NewReader(rdr)
	if path == "@" {
		var paths []string
		for {
			hdr, err := tr.Next()
			if err == io.EOF {
				break // End of archive
			}
			if err != nil {
				log.Error(err)
			}
			paths = append(paths, hdr.Name)
		}

		httpsrv.WriteObject(w, http.StatusOK, paths)
		return
	}

	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			break // End of archive
		}
		if err != nil {
			log.Error(err)
		}
		if path == "~" && hdr.Name == "index.html" {
			path = hdr.Name
		}
		if path == "~" && hdr.Name == "index.md" {
			path = hdr.Name
		}

		if hdr.Name == path {
			if strings.HasSuffix(hdr.Name, ".md") {
				md, err := ioutil.ReadAll(tr)
				if err != nil {
					httpsrv.WriteError(w, http.StatusInternalServerError, err.Error())
					return
				}

				w.Header().Set("Content-Type", "text/html")
				extensions := parser.CommonExtensions | parser.AutoHeadingIDs
				p := parser.NewWithExtensions(extensions)

				htmlFlags := html.CommonFlags | html.HrefTargetBlank
				opts := html.RendererOptions{Flags: htmlFlags}
				renderer := html.NewRenderer(opts)

				b := markdown.ToHTML(md, p, renderer)
				_, _ = w.Write(b)
				w.WriteHeader(http.StatusOK)
				return
			}

			pr := readutil.NewPreviewReader(tr)

			mime, err := readutil.ReadMIME(pr, hdr.Name)
			if err != nil {
				httpsrv.WriteError(w, http.StatusInternalServerError, err.Error())
				return
			}
			w.Header().Set("Content-Type", mime)

			if _, err := io.Copy(w, pr.Drain()); err != nil {
				log.Error(err)
			}
			w.WriteHeader(http.StatusOK)
			return
		}
	}

	httpsrv.WriteError(w, http.StatusNotFound, "Not Found in Archive")
}
func (a *Artifact) put(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

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

	rdr := io.LimitReader(r.Body, 500*1024*1024)
	pr := readutil.NewPreviewReader(rdr)
	rdr = pr.Drain()

	s256 := sha256.New()
	tmp, err := ioutil.TempFile(a.store, "arch-")
	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())
	}
	tmp.Close()

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

	log.Debugs("Artifact: moving file", "src", tmp.Name(), "dst", fname)
	_ = os.Rename(tmp.Name(), fname)

	httpsrv.WriteText(w, http.StatusCreated, id)
}
func (a *Artifact) list(w http.ResponseWriter, r *http.Request) {
	err := filepath.Walk(a.store, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}

		_, err = fmt.Fprintln(w, "FILE: ", info.Name())
		return err
	})
	if err != nil {
		httpsrv.WriteError(w, http.StatusInternalServerError, err.Error())
		return
	}
}
