package main

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"time"
)

type UploadData struct {
	User    UserInfo
	Results []Result
}

func detectMimeType(path string) (string, error) {
	file, err := os.Open(path)
	if err != nil {
		return "", err
	}

	buffer := make([]byte, 512)
	_, err = file.Read(buffer)
	if err != nil {
		return "", err
	}

	return http.DetectContentType(buffer), nil
}

func generateId() string {
	buffer := make([]byte, 4)
	rand.Read(buffer)

	return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(buffer)
}

func writeBody(reader io.ReadCloser, path string) error {
	out, err := os.Create(path)
	if err != nil {
		return err
	}
	defer out.Close()

	_, err = io.Copy(out, reader)
	if err != nil {
		return err
	}
	return out.Close()
}

func createImage(config *Config, body io.ReadCloser, fileHeader *multipart.FileHeader) (Image, error) {
	id := generateId()
	path := filepath.Join(config.SourceFolder, id)

	err := writeBody(body, path)
	if err != nil {
		return Image{}, err
	}

	mimeType, err := detectMimeType(path)
	if err != nil {
		return Image{}, err
	}

	image := Image{
		Id:           id,
		OriginalName: filepath.Base(fileHeader.Filename),
		CreatedAt:    time.Now(),
		MimeType:     mimeType,
	}
	return image, nil
}

func pageUpload(ctx PageContext) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "POST" {
			user := parseUser(r)

			err := r.ParseMultipartForm(32 << 20)
			if err != nil {
				if err = returnJson(w, []Result{{
					Success: false,
					Errors:  []string{err.Error()},
				}}); err != nil {
					panic(err)
				}
			}

			file, header, err := r.FormFile("file")
			if err != nil {
				if err = returnJson(w, []Result{{
					Success: false,
					Errors:  []string{err.Error()},
				}}); err != nil {
					panic(err)
				}
				return
			}
			image, err := createImage(ctx.Config, file, header)
			if err != nil {
				if err = returnJson(w, []Result{{
					Success: false,
					Errors:  []string{err.Error()},
				}}); err != nil {
					panic(err)
				}
				return
			}

			pubsub := ctx.Redis.Subscribe(ctx.Config.ResultChannel)
			_, err = ctx.Database.Exec("INSERT INTO images (id, owner, created_at, updated_at, original_name, type) VALUES ($1, $2, $3, $4, $5, $6)", image.Id, user.Id, image.CreatedAt, image.CreatedAt, image.OriginalName, image.MimeType)
			if err != nil {
				panic(err)
			}

			data, err := json.Marshal(image)
			if err != nil {
				if err = returnJson(w, []Result{{
					Success: false,
					Errors:  []string{err.Error()},
				}}); err != nil {
					panic(err)
				}
				return
			}

			fmt.Printf("Created task %s at %d\n", image.Id, time.Now().Unix())
			ctx.Redis.RPush(fmt.Sprintf("queue:%s", ctx.Config.ImageQueue), data)
			fmt.Printf("Submitted task %s at %d\n", image.Id, time.Now().Unix())

			waiting := true
			for waiting {
				message, err := pubsub.ReceiveMessage()
				if err != nil {
					if err = returnJson(w, []Result{{
						Success: false,
						Errors:  []string{err.Error()},
					}}); err != nil {
						panic(err)
					}
					return
				}

				result := Result{}
				err = json.Unmarshal([]byte(message.Payload), &result)
				if err != nil {
					if err = returnJson(w, []Result{{
						Success: false,
						Errors:  []string{err.Error()},
					}}); err != nil {
						panic(err)
					}
					return
				}

				fmt.Printf("Returned task %s at %d\n", result.Id, time.Now().Unix())

				if result.Id == image.Id {
					waiting = false

					if err = returnJson(w, result); err != nil {
						panic(err)
					}
				}
			}
			return
		} else {
			user := parseUser(r)
			if err := formatTemplate(w, "upload.html", UploadData{
				user,
				[]Result{},
			}); err != nil {
				panic(err)
			}
		}
	})
}