package upyun

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"time"
)

type FormUploadConfig struct {
	LocalPath      string
	SaveKey        string
	ExpireAfterSec int64
	NotifyUrl      string
	Apps           []map[string]interface{}
	Options        map[string]interface{}
}

type FormUploadResp struct {
	Code      int      `json:"code"`
	Msg       string   `json:"message"`
	Url       string   `json:"url"`
	Timestamp int64    `json:"time"`
	ImgWidth  int      `json:"image-width"`
	ImgHeight int      `json:"image-height"`
	ImgFrames int      `json:"image-frames"`
	ImgType   string   `json:"image-type"`
	Sign      string   `json:"sign"`
	Taskids   []string `json:"task_ids"`
}

func (config *FormUploadConfig) Format() {
	if config.Options == nil {
		config.Options = make(map[string]interface{})
	}
	if config.SaveKey != "" {
		config.Options["save-key"] = config.SaveKey
	}
	if config.NotifyUrl != "" {
		config.Options["notify-url"] = config.NotifyUrl
	}
	if config.ExpireAfterSec > 0 {
		config.Options["expiration"] = time.Now().Unix() + config.ExpireAfterSec
	}
	if len(config.Apps) > 0 {
		config.Options["apps"] = config.Apps
	}
}

func (up *UpYun) FormUpload(config *FormUploadConfig) (*FormUploadResp, error) {
	config.Format()
	config.Options["bucket"] = up.Bucket

	args, err := json.Marshal(config.Options)
	if err != nil {
		return nil, err
	}
	policy := base64ToStr(args)

	formValues := make(map[string]string)
	formValues["policy"] = policy
	formValues["file"] = config.LocalPath

	if up.deprecated {
		formValues["signature"] = up.MakeFormAuth(policy)
	} else {
		sign := &UnifiedAuthConfig{
			Method: "POST",
			Uri:    "/" + up.Bucket,
			Policy: policy,
		}
		if v, ok := config.Options["date"]; ok {
			sign.DateStr = v.(string)
		}
		if v, ok := config.Options["content-md5"]; ok {
			sign.ContentMD5 = v.(string)
		}
		formValues["authorization"] = up.MakeUnifiedAuth(sign)
	}

	endpoint := up.doGetEndpoint("v0.api.upyun.com")
	url := fmt.Sprintf("http://%s/%s", endpoint, up.Bucket)
	resp, err := up.doFormRequest(url, formValues)
	if err != nil {
		return nil, err
	}

	b, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()

	if err != nil {
		return nil, errorOperation("form read body", err)
	}

	var r FormUploadResp
	err = json.Unmarshal(b, &r)
	return &r, err
}

func (up *UpYun) doFormRequest(url string, formValues map[string]string) (*http.Response, error) {
	formBody := &bytes.Buffer{}
	formWriter := multipart.NewWriter(formBody)
	defer formWriter.Close()

	for k, v := range formValues {
		if k != "file" {
			formWriter.WriteField(k, v)
		}
	}

	boundary := formWriter.Boundary()
	bdBuf := bytes.NewBufferString(fmt.Sprintf("\r\n--%s--\r\n", boundary))

	fpath := formValues["file"]
	fd, err := os.Open(fpath)
	if err != nil {
		return nil, err
	}
	defer fd.Close()

	fInfo, err := fd.Stat()
	if err != nil {
		return nil, err
	}

	_, err = formWriter.CreateFormFile("file", filepath.Base(fpath))
	if err != nil {
		return nil, err
	}

	headers := map[string]string{
		"Content-Type":   "multipart/form-data; boundary=" + boundary,
		"Content-Length": fmt.Sprint(formBody.Len() + int(fInfo.Size()) + bdBuf.Len()),
	}

	body := io.MultiReader(formBody, fd, bdBuf)
	resp, err := up.doHTTPRequest("POST", url, headers, body)
	if err != nil {
		return nil, errorOperation("form", err)
	}
	return resp, nil
}
