package protocol

import (
	"bytes"
	"encoding/binary"

	"github.com/nadoo/glider/proxy/ssr/internal/ssr"
	"github.com/nadoo/glider/proxy/ssr/internal/tools"
)

func init() {
	register("verify_sha1", NewVerifySHA1)
	register("ota", NewVerifySHA1)
}

type verifySHA1 struct {
	ssr.ServerInfo
	hasSentHeader bool
	buffer        bytes.Buffer
	chunkId       uint32
}

const (
	oneTimeAuthMask byte = 0x10
)

func NewVerifySHA1() IProtocol {
	a := &verifySHA1{}
	return a
}

func (v *verifySHA1) otaConnectAuth(data []byte) []byte {
	return append(data, tools.HmacSHA1(append(v.IV, v.Key...), data)...)
}

func (v *verifySHA1) otaReqChunkAuth(chunkId uint32, data []byte) []byte {
	nb := make([]byte, 2)
	binary.BigEndian.PutUint16(nb, uint16(len(data)))
	chunkIdBytes := make([]byte, 4)
	binary.BigEndian.PutUint32(chunkIdBytes, chunkId)
	header := append(nb, tools.HmacSHA1(append(v.IV, chunkIdBytes...), data)...)
	return append(header, data...)
}

func (v *verifySHA1) otaVerifyAuth(iv []byte, chunkId uint32, data []byte, expectedHmacSha1 []byte) bool {
	chunkIdBytes := make([]byte, 4)
	binary.BigEndian.PutUint32(chunkIdBytes, chunkId)
	actualHmacSha1 := tools.HmacSHA1(append(iv, chunkIdBytes...), data)
	return bytes.Equal(expectedHmacSha1, actualHmacSha1)
}

func (v *verifySHA1) getAndIncreaseChunkId() (chunkId uint32) {
	chunkId = v.chunkId
	v.chunkId += 1
	return
}

func (v *verifySHA1) SetServerInfo(s *ssr.ServerInfo) {
	v.ServerInfo = *s
}

func (v *verifySHA1) GetServerInfo() (s *ssr.ServerInfo) {
	return &v.ServerInfo
}

func (v *verifySHA1) SetData(data interface{}) {

}

func (v *verifySHA1) GetData() interface{} {
	return nil
}

func (v *verifySHA1) PreEncrypt(data []byte) (encryptedData []byte, err error) {
	v.buffer.Reset()
	dataLength := len(data)
	offset := 0
	if !v.hasSentHeader {
		data[0] |= oneTimeAuthMask
		v.buffer.Write(v.otaConnectAuth(data[:v.HeadLen]))
		v.hasSentHeader = true
		dataLength -= v.HeadLen
		offset += v.HeadLen
	}
	const blockSize = 4096
	for dataLength > blockSize {
		chunkId := v.getAndIncreaseChunkId()
		v.buffer.Write(v.otaReqChunkAuth(chunkId, data[offset:offset+blockSize]))
		dataLength -= blockSize
		offset += blockSize
	}
	if dataLength > 0 {
		chunkId := v.getAndIncreaseChunkId()
		v.buffer.Write(v.otaReqChunkAuth(chunkId, data[offset:]))
	}
	return v.buffer.Bytes(), nil
}

func (v *verifySHA1) PostDecrypt(data []byte) ([]byte, int, error) {
	return data, len(data), nil
}

func (v *verifySHA1) GetOverhead() int {
	return 0
}
