package writefreely

import (
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/hashicorp/go-multierror"
	"github.com/writeas/impart"
	wfimport "github.com/writeas/import"
	"github.com/writeas/web-core/log"
)

func viewImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
	// Fetch extra user data
	p := NewUserPage(app, r, u, "Import", nil)

	c, err := app.db.GetCollections(u)
	if err != nil {
		return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("unable to fetch collections: %v", err)}
	}

	d := struct {
		*UserPage
		Collections *[]Collection
		Flashes     []template.HTML
		Message     string
		InfoMsg     bool
	}{
		UserPage:    p,
		Collections: c,
		Flashes:     []template.HTML{},
	}

	flashes, _ := getSessionFlashes(app, w, r, nil)
	for _, flash := range flashes {
		if strings.HasPrefix(flash, "SUCCESS: ") {
			d.Message = strings.TrimPrefix(flash, "SUCCESS: ")
		} else if strings.HasPrefix(flash, "INFO: ") {
			d.Message = strings.TrimPrefix(flash, "INFO: ")
			d.InfoMsg = true
		} else {
			d.Flashes = append(d.Flashes, template.HTML(flash))
		}
	}

	showUserPage(w, "import", d)
	return nil
}

func handleImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
	// limit 10MB per submission
	// TODO: increase?
	r.ParseMultipartForm(10 << 20)
	files := r.MultipartForm.File["files"]
	filesSubmitted := len(files)
	var filesImported, collsImported int
	var errs []error
	// TODO: support multiple zip uploads at once
	if filesSubmitted == 1 && files[0].Header.Get("Content-Type") == "application/zip" {
		filesSubmitted, filesImported, collsImported, errs = importZipPosts(app, w, r, files[0], u)
	} else {
		filesImported, errs = importFilePosts(app, w, r, files, u)
	}

	if len(errs) != 0 {
		_ = addSessionFlash(app, w, r, multierror.ListFormatFunc(errs), nil)
	}
	if filesImported == filesSubmitted && filesSubmitted != 0 {
		postAdj := "posts"
		if filesSubmitted == 1 {
			postAdj = "post"
		}
		if collsImported != 0 {
			collAdj := "collections"
			if collsImported == 1 {
				collAdj = "collection"
			}
			_ = addSessionFlash(app, w, r, fmt.Sprintf(
				"SUCCESS: Import complete, %d %s imported across %d %s.",
				filesImported,
				postAdj,
				collsImported,
				collAdj,
			), nil)
		} else {
			_ = addSessionFlash(app, w, r, fmt.Sprintf("SUCCESS: Import complete, %d %s imported.", filesImported, postAdj), nil)
		}
	} else if filesImported == 0 && filesSubmitted == 0 {
		_ = addSessionFlash(app, w, r, "INFO: 0 valid posts found", nil)
	} else if filesImported > 0 {
		_ = addSessionFlash(app, w, r, fmt.Sprintf("INFO: %d of %d posts imported, see details below.", filesImported, filesSubmitted), nil)
	}
	return impart.HTTPError{http.StatusFound, "/me/import"}
}

func importFilePosts(app *App, w http.ResponseWriter, r *http.Request, files []*multipart.FileHeader, u *User) (int, []error) {
	var fileErrs []error
	var count int
	for _, formFile := range files {
		if filepath.Ext(formFile.Filename) == ".zip" {
			fileErrs = append(fileErrs, fmt.Errorf("zips are supported as a single upload only: %s", formFile.Filename))
			log.Info("zip included in bulk files, skipping")
			continue
		}
		info, err := formFileToTemp(formFile)
		if err != nil {
			fileErrs = append(fileErrs, fmt.Errorf("failed to get file info of: %s", formFile.Filename))
			log.Error("import textfile: stat temp file: %v", err)
			continue
		}
		post, err := wfimport.FromFile(filepath.Join(os.TempDir(), info.Name()))
		if err == wfimport.ErrEmptyFile {
			// not a real error so don't log
			_ = addSessionFlash(app, w, r, fmt.Sprintf("%s was empty, import skipped", formFile.Filename), nil)
			continue
		} else if err == wfimport.ErrInvalidContentType {
			// same as above
			_ = addSessionFlash(app, w, r, fmt.Sprintf("%s is not a supported post file", formFile.Filename), nil)
			continue
		} else if err != nil {
			fileErrs = append(fileErrs, fmt.Errorf("failed to read copy of %s", formFile.Filename))
			log.Error("import textfile: file to post: %v", err)
			continue
		}

		post.Collection = r.PostFormValue("collection")
		coll, _ := app.db.GetCollection(post.Collection)
		if coll == nil {
			coll = &Collection{
				ID: 0,
			}
		}
		coll.hostName = app.cfg.App.Host
		created := post.Created.Format("2006-01-02T15:04:05Z")
		submittedPost := SubmittedPost{
			Title:   &post.Title,
			Content: &post.Content,
			Font:    "norm",
			Created: &created,
		}
		rp, err := app.db.CreatePost(u.ID, coll.ID, &submittedPost)
		if err != nil {
			fileErrs = append(fileErrs, fmt.Errorf("failed to create post from %s", formFile.Filename))
			log.Error("import textfile: create db post: %v", err)
			continue
		}

		// create public post

		if coll.ID != 0 && app.cfg.App.Federation {
			go federatePost(
				app,
				&PublicPost{
					Post: rp,
					Collection: &CollectionObj{
						Collection: *coll,
					},
				},
				coll.ID,
				false,
			)
		}
		count++
	}
	return count, fileErrs
}

func importZipPosts(app *App, w http.ResponseWriter, r *http.Request, file *multipart.FileHeader, u *User) (filesSubmitted, importedPosts, importedColls int, errs []error) {
	info, err := formFileToTemp(file)
	if err != nil {
		errs = append(errs, fmt.Errorf("upload temp file: %v", err))
		return
	}

	postMap, err := wfimport.FromZipDirs(filepath.Join(os.TempDir(), info.Name()))
	if err != nil {
		errs = append(errs, fmt.Errorf("parse posts and collections from zip: %v", err))
		return
	}

	for collKey, posts := range postMap {
		if len(posts) == 0 {
			continue
		}
		collObj := CollectionObj{}
		if collKey != wfimport.DraftsKey {
			coll, err := app.db.GetCollection(collKey)
			if err == ErrCollectionNotFound {
				coll, err = app.db.CreateCollection(app.cfg, collKey, collKey, u.ID)
				if err != nil {
					errs = append(errs, fmt.Errorf("create non existent collection: %v", err))
					continue
				}
				coll.hostName = app.cfg.App.Host
				collObj.Collection = *coll
			} else if err != nil {
				errs = append(errs, fmt.Errorf("get collection: %v", err))
				continue
			}
			collObj.Collection = *coll
			importedColls++
		}

		for _, post := range posts {
			if post != nil {
				filesSubmitted++
				created := post.Created.Format("2006-01-02T15:04:05Z")
				submittedPost := SubmittedPost{
					Title:   &post.Title,
					Content: &post.Content,
					Font:    "norm",
					Created: &created,
				}
				rp, err := app.db.CreatePost(u.ID, collObj.Collection.ID, &submittedPost)
				if err != nil {
					errs = append(errs, fmt.Errorf("create post: %v", err))
					continue
				}

				if collObj.Collection.ID != 0 && app.cfg.App.Federation {
					go federatePost(
						app,
						&PublicPost{
							Post:       rp,
							Collection: &collObj,
						},
						collObj.Collection.ID,
						false,
					)
				}
				importedPosts++
			}
		}
	}
	return
}

func formFileToTemp(formFile *multipart.FileHeader) (os.FileInfo, error) {
	file, err := formFile.Open()
	if err != nil {
		return nil, fmt.Errorf("failed to open form file: %s", formFile.Filename)
	}
	defer file.Close()

	tempFile, err := ioutil.TempFile("", fmt.Sprintf("upload-*%s", filepath.Ext(formFile.Filename)))
	if err != nil {
		return nil, fmt.Errorf("failed to create temporary file for: %s", formFile.Filename)
	}
	defer tempFile.Close()

	_, err = io.Copy(tempFile, file)
	if err != nil {
		return nil, fmt.Errorf("failed to copy file into temporary location: %s", formFile.Filename)
	}

	return tempFile.Stat()
}
