package tablestore

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"hash"
	"sort"
	"strings"
)

const (
	xOtsDate                    = "x-ots-date"
	xOtsApiversion              = "x-ots-apiversion"
	xOtsAccesskeyid             = "x-ots-accesskeyid"
	xOtsContentmd5              = "x-ots-contentmd5"
	xOtsHeaderStsToken          = "x-ots-ststoken"
	xOtsHeaderChargeAdmin       = "x-ots-charge-for-admin"
	xOtsSignature               = "x-ots-signature"
	xOtsRequestCompressType     = "x-ots-request-compress-type"
	xOtsRequestCompressSize     = "x-ots-request-compress-size"
	xOtsResponseCompressTye     = "x-ots-response-compress-type"
	xOtsHeaderSDKTraceID        = "x-ots-sdk-traceid"
	xOtsHeaderRequestPriority   = "x-ots-request-priority"
	xOtsHeaderRequestTag        = "x-ots-request-tag"
	xOtsHeaderSourceIp          = "x-ots-sourceip"
	xOtsHeaderIsSecureTransport = "x-ots-issecuretransport"
	xOtsHeaderTunnelType        = "x-ots-tunnel-type"
	xOtsHeaderPlayerAccountId   = "x-ots-playeraccountid"
	XOtsHeaderAdminTaskType     = "x-ots-admin-task-type"
	XOtsHeaderAdminTargetUserId = "x-ots-admin-target-userid"
	xOtsPrefix                  = "x-ots"
)

type otsHeader struct {
	name  string
	value string
	must  bool
}

type otsHeaders struct {
	headers  []*otsHeader
	hmacSha1 hash.Hash
}

func createOtsHeaders(accessKey string) *otsHeaders {
	h := new(otsHeaders)

	h.headers = []*otsHeader{
		&otsHeader{name: xOtsDate, must: true},
		&otsHeader{name: xOtsApiversion, must: true},
		&otsHeader{name: xOtsAccesskeyid, must: true},
		&otsHeader{name: xOtsContentmd5, must: true},
		&otsHeader{name: xOtsInstanceName, must: true},
		&otsHeader{name: xOtsSignature, must: true},
		&otsHeader{name: xOtsRequestCompressSize, must: false},
		&otsHeader{name: xOtsResponseCompressTye, must: false},
		&otsHeader{name: xOtsRequestCompressType, must: false},
		&otsHeader{name: xOtsHeaderStsToken, must: false},
		&otsHeader{name: xOtsHeaderChargeAdmin, must: false},
		&otsHeader{name: xOtsHeaderTunnelType, must: false},
		&otsHeader{name: xOtsHeaderPlayerAccountId, must: false},
		&otsHeader{name: XOtsHeaderAdminTaskType, must: false},
		&otsHeader{name: XOtsHeaderAdminTargetUserId, must: false},
		&otsHeader{name: xOtsHeaderSDKTraceID, must: false},
		&otsHeader{name: xOtsHeaderRequestPriority, must: false},
		&otsHeader{name: xOtsHeaderRequestTag, must: false},
		&otsHeader{name: xOtsHeaderSourceIp, must: false},
		&otsHeader{name: xOtsHeaderIsSecureTransport, must: false},
	}

	sort.Sort(h)

	h.hmacSha1 = hmac.New(sha1.New, []byte(accessKey))
	return h
}

func (h *otsHeaders) Len() int {
	return len(h.headers)
}

func (h *otsHeaders) Swap(i, j int) {
	h.headers[i], h.headers[j] = h.headers[j], h.headers[i]
}

func (h *otsHeaders) Less(i, j int) bool {
	if h.headers[i].name == xOtsSignature {
		return false
	}

	if h.headers[j].name == xOtsSignature {
		return true
	}

	return h.headers[i].name < h.headers[j].name
}

func (h *otsHeaders) search(name string) *otsHeader {
	index := sort.Search(len(h.headers)-1, func(i int) bool {
		return h.headers[i].name >= name
	})

	if index >= len(h.headers) {
		return nil
	}

	return h.headers[index]
}

func (h *otsHeaders) set(name, value string) {
	header := h.search(name)
	if header == nil {
		return
	}

	header.value = value
}

func (h *otsHeaders) signature(uri, method, accessKey string) (string, error) {
	for _, header := range h.headers[:len(h.headers)-1] {
		if header.must && header.value == "" {
			return "", errMissMustHeader(header.name)
		}
	}

	// StringToSign = CanonicalURI + '\n' + HTTPRequestMethod + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n'
	// TODO CanonicalQueryString 为空
	stringToSign := uri + "\n" + method + "\n" + "\n"

	// 最后一个header 为 xOtsSignature
	for _, header := range h.headers[:len(h.headers)-1] {
		if header.value != "" {
			stringToSign = stringToSign + header.name + ":" + strings.TrimSpace(header.value) + "\n"
		}
	}

	h.hmacSha1.Reset()
	h.hmacSha1.Write([]byte(stringToSign))

	sign := base64.StdEncoding.EncodeToString(h.hmacSha1.Sum(nil))
	h.set(xOtsSignature, sign)
	return sign, nil
}
