package contented

import (
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"io"
	"log"
	"mime"
	"mime/multipart"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"
)

func (this *Server) handleUpload(w http.ResponseWriter, r *http.Request) {

	remoteIP := this.remoteIP(r)

	err := r.ParseMultipartForm(this.opts.MaxUploadBytes * 2)
	if err != nil {
		log.Printf("%s Invalid request: %s\n", remoteIP, err.Error())
		http.Error(w, "Invalid request", 400)
		return
	}

	if r.MultipartForm == nil || r.MultipartForm.File == nil || len(r.MultipartForm.File["f"]) < 1 {
		log.Printf("%s Invalid request: no multipart content\n", remoteIP)
		http.Error(w, "Invalid request", 400)
		return
	}

	ret := make([]string, 0, len(r.MultipartForm.File["f"]))

	for _, fhs := range r.MultipartForm.File["f"] {
		f, err := fhs.Open()
		if err != nil {
			log.Printf("%s Internal error: %s\n", remoteIP, err.Error())
			http.Error(w, "Internal error", 500)
			return
		}

		path, err := this.handleUploadFile(f, fhs, remoteIP)
		if err != nil {
			log.Printf("%s Upload failed: %s\n", remoteIP, err.Error())
			http.Error(w, "Upload failed", 500)
		}

		ret = append(ret, path)
	}

	jb, err := json.Marshal(ret)
	if err != nil {
		log.Printf("%s Internal error: %s\n", remoteIP, err.Error())
		http.Error(w, "Internal error", 500)
		return
	}

	w.Header().Set(`Content-Type`, `application/json`)
	w.WriteHeader(200)
	w.Write(jb)
}

func (this *Server) handleUploadFile(src multipart.File, hdr *multipart.FileHeader, UploadIP string) (string, error) {

	// Get file length
	srcLen, err := src.Seek(0, io.SeekEnd)
	if err != nil {
		return "", err
	}

	_, err = src.Seek(0, io.SeekStart)
	if err != nil {
		return "", err
	}

	// Get file hash
	hasher := sha512.New512_256()
	_, err = io.CopyN(hasher, src, int64(srcLen))
	if err != nil {
		return "", err
	}

	_, err = src.Seek(0, io.SeekStart)
	if err != nil {
		return "", err
	}

	// Save file to disk
	fileHash := hex.EncodeToString(hasher.Sum(nil))
	dest, err := os.OpenFile(filepath.Join(this.opts.DataDirectory, fileHash), os.O_CREATE|os.O_WRONLY, this.opts.FileMode())
	shouldSave := true
	if err != nil && os.IsExist(err) {
		// hash matches existing upload
		// That's fine - but still persist the metadata separately
		shouldSave = false
	} else if err != nil {
		return "", err
	}

	if shouldSave {
		defer dest.Close()

		_, err = io.CopyN(dest, src, int64(srcLen))
		if err != nil {
			return "", err
		}
	}

	// Determine mime type
	ctype := hdr.Header.Get("Content-Type")
	if ctype == "" {
		ctype = mime.TypeByExtension(path.Ext(hdr.Filename))
	}
	if ctype == "" {
		ctype = `application/octet-stream`
	}

	// Blacklisted mime-types
	ctype = strings.ToLower(ctype)
	if strings.HasPrefix(ctype, `text/html`) ||
		strings.HasPrefix(ctype, `application/javascript`) {
		ctype = `application/octet-stream`
	}

	// Persist metadata to DB
	m := Metadata{
		FileHash:   fileHash,
		Filename:   hdr.Filename,
		UploadTime: time.Now(),
		UploadIP:   UploadIP,
		FileSize:   srcLen,
		MimeType:   ctype,
	}

	fileRef, err := this.AddMetadata(m)
	if err != nil {
		return "", err
	}

	// Done
	return fileRef, nil
}
