package qbox

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"io"
	"net/http"

	. "qiniupkg.com/api.v7/conf"
	"qiniupkg.com/x/bytes.v7/seekable"
)

// ----------------------------------------------------------

type Mac struct {
	AccessKey string
	SecretKey []byte
}

func NewMac(accessKey, secretKey string) (mac *Mac) {

	if accessKey == "" {
		accessKey, secretKey = ACCESS_KEY, SECRET_KEY
	}
	return &Mac{accessKey, []byte(secretKey)}
}

func (mac *Mac) Sign(data []byte) (token string) {

	h := hmac.New(sha1.New, mac.SecretKey)
	h.Write(data)

	sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
	return mac.AccessKey + ":" + sign[:27]
}

func (mac *Mac) SignWithData(b []byte) (token string) {

	blen := base64.URLEncoding.EncodedLen(len(b))

	key := mac.AccessKey
	nkey := len(key)
	ret := make([]byte, nkey+30+blen)

	base64.URLEncoding.Encode(ret[nkey+30:], b)

	h := hmac.New(sha1.New, mac.SecretKey)
	h.Write(ret[nkey+30:])
	digest := h.Sum(nil)

	copy(ret, key)
	ret[nkey] = ':'
	base64.URLEncoding.Encode(ret[nkey+1:], digest)
	ret[nkey+29] = ':'

	return string(ret)
}

func (mac *Mac) SignRequest(req *http.Request, incbody bool) (token string, err error) {

	h := hmac.New(sha1.New, mac.SecretKey)

	u := req.URL
	data := u.Path
	if u.RawQuery != "" {
		data += "?" + u.RawQuery
	}
	io.WriteString(h, data+"\n")

	if incbody {
		s2, err2 := seekable.New(req)
		if err2 != nil {
			return "", err2
		}
		h.Write(s2.Bytes())
	}

	sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
	token = mac.AccessKey + ":" + sign
	return
}

func (mac *Mac) VerifyCallback(req *http.Request) (bool, error) {

	auth := req.Header.Get("Authorization")
	if auth == "" {
		return false, nil
	}

	token, err := mac.SignRequest(req, true)
	if err != nil {
		return false, err
	}

	return auth == "QBox "+token, nil
}

// ---------------------------------------------------------------------------------------

func Sign(mac *Mac, data []byte) string {

	if mac == nil {
		mac = NewMac(ACCESS_KEY, SECRET_KEY)
	}
	return mac.Sign(data)
}

func SignWithData(mac *Mac, data []byte) string {

	if mac == nil {
		mac = NewMac(ACCESS_KEY, SECRET_KEY)
	}
	return mac.SignWithData(data)
}

// ---------------------------------------------------------------------------------------

type Transport struct {
	mac       Mac
	Transport http.RoundTripper
}

func incBody(req *http.Request) bool {

	if req.Body == nil {
		return false
	}
	if ct, ok := req.Header["Content-Type"]; ok {
		switch ct[0] {
		case "application/x-www-form-urlencoded":
			return true
		}
	}
	return false
}

func (t *Transport) NestedObject() interface{} {

	return t.Transport
}

func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {

	token, err := t.mac.SignRequest(req, incBody(req))
	if err != nil {
		return
	}
	req.Header.Set("Authorization", "QBox "+token)
	return t.Transport.RoundTrip(req)
}

func NewTransport(mac *Mac, transport http.RoundTripper) *Transport {

	if mac == nil {
		mac = NewMac(ACCESS_KEY, SECRET_KEY)
	}
	if transport == nil {
		transport = http.DefaultTransport
	}
	t := &Transport{mac: *mac, Transport: transport}
	return t
}

func NewClient(mac *Mac, transport http.RoundTripper) *http.Client {

	t := NewTransport(mac, transport)
	return &http.Client{Transport: t}
}

// ---------------------------------------------------------------------------------------
