package trantor

import (
	log "github.com/cihub/seelog"

	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"io"
	"mime/multipart"
	"net/http"

	"github.com/meskio/epubgo"
	"gitlab.com/trantor/trantor/lib/database"
	"gitlab.com/trantor/trantor/lib/parser"
	"gitlab.com/trantor/trantor/lib/storage"
)

const (
	uploadChanSize = 100
)

func InitUpload(database database.DB, store storage.Store) {
	uploadChannel = make(chan uploadRequest, uploadChanSize)
	go uploadWorker(database, store)
}

var uploadChannel chan uploadRequest

type uploadRequest struct {
	file   multipart.File
	header *multipart.FileHeader
	id     int
}

func uploadWorker(database database.DB, store storage.Store) {
	for req := range uploadChannel {
		req.processFile(database, store)
	}
}

func (req uploadRequest) processFile(db database.DB, store storage.Store) {
	defer req.file.Close()

	h := sha256.New()
	if _, err := io.Copy(h, req.file); err != nil {
		log.Warn("Can't read uploaded file ", req.header.Filename, ": ", err)
		db.UpdateSubmission(req.id, "There was a problem with the uploaded book, try again later.", nil)
		return
	}
	fileHash := h.Sum(nil)
	if db.ExistsBookHash(fileHash) {
		log.Info("Uploaded file ", req.header.Filename, " already in the library.")
		db.UpdateSubmission(req.id, "The uploaded book is already in the library (it could be in the moderation queue)", nil)
		return
	}

	epub, err := epubgo.Load(req.file, req.header.Size)
	if err != nil {
		log.Warn("Not valid epub uploaded file ", req.header.Filename, ": ", err)
		db.UpdateSubmission(req.id, "It is not a valid epub file", nil)
		return
	}
	defer epub.Close()

	book := parser.EpubMetadata(epub)
	book.ID = GenID()

	req.file.Seek(0, 0)
	size, err := store.Store(book.ID, req.file, epubFile)
	if err != nil {
		log.Error("Error storing book (", book.ID, "): ", err)
		db.UpdateSubmission(req.id, "There was a problem in our server", nil)
		return
	}

	book.FileSize = int(size)
	book.FileHash = fileHash
	book.Cover = GetCover(epub, book.ID, store)

	err = db.AddBook(book)
	if err != nil {
		log.Error("Error storing metadata (", book.ID, "): ", err)
		db.UpdateSubmission(req.id, "There was a problem in our server", nil)
		return
	}
	log.Info("File uploaded: ", req.header.Filename)
	db.UpdateSubmission(req.id, "Waiting for moderation", &book)
}

func uploadPostHandler(h handler) {
	const _2M int64 = (1 << 20) * 2

	if h.ro {
		h.sess.Notify("Upload failed!", "The library is in Read Only mode, no books can be uploaded", "danger")
		uploadHandler(h)
		return
	}

	if err := h.r.ParseMultipartForm(_2M); nil != err {
		log.Error("Can't parse form: ", err)
		return
	}
	defer h.r.MultipartForm.RemoveAll()

	filesForm := h.r.MultipartForm.File["epub"]
	submissionID := GenID()
	for _, f := range filesForm {
		submission := database.Submission{
			SubmissionID: submissionID,
			Filename:     f.Filename,
			Status:       "Waiting to be processed, reload the page in few minutes",
		}

		file, err := f.Open()
		if err != nil {
			log.Error("Can not open uploaded file ", f.Filename, ": ", err)
			h.sess.Notify("Upload problem!", "There was a problem with book "+f.Filename, "danger")
			submission.Status = "It was not possible to read the book"
			h.db.AddSubmission(submission, h.sess.User)
			continue
		}

		id, err := h.db.AddSubmission(submission, h.sess.User)
		if err != nil {
			log.Error("Can add submission to db for ", f.Filename, ": ", err)
		}
		uploadChannel <- uploadRequest{file, f, id}
	}
	http.Redirect(h.w, h.r, "/submission/"+submissionID, http.StatusFound)
}

func uploadHandler(h handler) {
	var data struct{ S Status }
	data.S = GetStatus(h)
	data.S.Title = "Upload -- " + data.S.Title
	data.S.Upload = true
	h.load("upload", data)
}

func GenID() string {
	b := make([]byte, 12)
	rand.Read(b)
	return base64.URLEncoding.EncodeToString(b)
}