package hap

import (
	"bufio"
	"crypto/sha512"
	"errors"
	"fmt"
	"net/http"

	"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
	"github.com/AlexxIT/go2rtc/pkg/hap/curve25519"
	"github.com/AlexxIT/go2rtc/pkg/hap/ed25519"
	"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
	"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
	"github.com/tadglines/go-pkgs/crypto/srp"
)

type Server struct {
	Pin           string
	DeviceID      string
	DevicePrivate []byte

	// GetClientPublic may be nil, so client validation will be disabled
	GetClientPublic func(id string) []byte
}

func (s *Server) ServerPublic() []byte {
	return s.DevicePrivate[32:]
}

//func (s *Server) Status() string {
//	if len(s.Pairings) == 0 {
//		return StatusNotPaired
//	}
//	return StatusPaired
//}

func (s *Server) PairSetup(req *http.Request, rw *bufio.ReadWriter) (id string, publicKey []byte, err error) {
	// STEP 1. Request from iPhone
	var plainM1 struct {
		State  byte   `tlv8:"6"`
		Method byte   `tlv8:"0"`
		Flags  uint32 `tlv8:"19"`
	}
	if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM1); err != nil {
		return
	}
	if plainM1.State != StateM1 {
		err = newRequestError(plainM1)
		return
	}

	username := []byte("Pair-Setup")

	// Stanford Secure Remote Password (SRP) / Password Authenticated Key Exchange (PAKE)
	pake, err := srp.NewSRP("rfc5054.3072", sha512.New, keyDerivativeFuncRFC2945(username))
	if err != nil {
		return
	}

	pake.SaltLength = 16

	salt, verifier, err := pake.ComputeVerifier([]byte(s.Pin))
	if err != nil {
		return
	}

	session := pake.NewServerSession(username, salt, verifier)

	// STEP 2. Response to iPhone
	plainM2 := struct {
		State     byte   `tlv8:"6"`
		PublicKey string `tlv8:"3"`
		Salt      string `tlv8:"2"`
	}{
		State:     StateM2,
		PublicKey: string(session.GetB()),
		Salt:      string(salt),
	}
	body, err := tlv8.Marshal(plainM2)
	if err != nil {
		return
	}
	if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
		return
	}

	// STEP 3. Request from iPhone
	if req, err = http.ReadRequest(rw.Reader); err != nil {
		return
	}

	var plainM3 struct {
		State     byte   `tlv8:"6"`
		PublicKey string `tlv8:"3"`
		Proof     string `tlv8:"4"`
	}
	if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM3); err != nil {
		return
	}
	if plainM3.State != StateM3 {
		err = newRequestError(plainM3)
		return
	}

	// important to compute key before verify client
	sessionShared, err := session.ComputeKey([]byte(plainM3.PublicKey))
	if err != nil {
		return
	}

	if !session.VerifyClientAuthenticator([]byte(plainM3.Proof)) {
		err = errors.New("hap: VerifyClientAuthenticator")
		return
	}

	proof := session.ComputeAuthenticator([]byte(plainM3.Proof)) // server proof

	// STEP 4. Response to iPhone
	payloadM4 := struct {
		State byte   `tlv8:"6"`
		Proof string `tlv8:"4"`
	}{
		State: StateM4,
		Proof: string(proof),
	}
	if body, err = tlv8.Marshal(payloadM4); err != nil {
		return
	}
	if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
		return
	}

	// STEP 5. Request from iPhone
	if req, err = http.ReadRequest(rw.Reader); err != nil {
		return
	}
	var cipherM5 struct {
		State         byte   `tlv8:"6"`
		EncryptedData string `tlv8:"5"`
	}
	if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &cipherM5); err != nil {
		return
	}
	if cipherM5.State != StateM5 {
		err = newRequestError(cipherM5)
		return
	}

	// decrypt message using session shared
	encryptKey, err := hkdf.Sha512(sessionShared, "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info")
	if err != nil {
		return
	}

	b, err := chacha20poly1305.Decrypt(encryptKey, "PS-Msg05", []byte(cipherM5.EncryptedData))
	if err != nil {
		return
	}

	// unpack message from TLV8
	var plainM5 struct {
		Identifier string `tlv8:"1"`
		PublicKey  string `tlv8:"3"`
		Signature  string `tlv8:"10"`
	}
	if err = tlv8.Unmarshal(b, &plainM5); err != nil {
		return
	}

	// 3. verify client ID and Public
	remoteSign, err := hkdf.Sha512(
		sessionShared, "Pair-Setup-Controller-Sign-Salt", "Pair-Setup-Controller-Sign-Info",
	)
	if err != nil {
		return
	}

	b = Append(remoteSign, plainM5.Identifier, plainM5.PublicKey)
	if !ed25519.ValidateSignature([]byte(plainM5.PublicKey), b, []byte(plainM5.Signature)) {
		err = errors.New("hap: ValidateSignature")
		return
	}

	// 4. generate signature to our ID and Public
	localSign, err := hkdf.Sha512(
		sessionShared, "Pair-Setup-Accessory-Sign-Salt", "Pair-Setup-Accessory-Sign-Info",
	)
	if err != nil {
		return
	}

	b = Append(localSign, s.DeviceID, s.ServerPublic()) // ServerPublic
	signature, err := ed25519.Signature(s.DevicePrivate, b)
	if err != nil {
		return
	}

	// 5. pack our ID and Public
	plainM6 := struct {
		Identifier string `tlv8:"1"`
		PublicKey  string `tlv8:"3"`
		Signature  string `tlv8:"10"`
	}{
		Identifier: s.DeviceID,
		PublicKey:  string(s.ServerPublic()),
		Signature:  string(signature),
	}
	if b, err = tlv8.Marshal(plainM6); err != nil {
		return
	}

	// 6. encrypt message
	b, err = chacha20poly1305.Encrypt(encryptKey, "PS-Msg06", b)
	if err != nil {
		return
	}

	// STEP 6. Response to iPhone
	cipherM6 := struct {
		State         byte   `tlv8:"6"`
		EncryptedData string `tlv8:"5"`
	}{
		State:         StateM6,
		EncryptedData: string(b),
	}
	if body, err = tlv8.Marshal(cipherM6); err != nil {
		return
	}
	if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
		return
	}

	id = plainM5.Identifier
	publicKey = []byte(plainM5.PublicKey)

	return
}

func (s *Server) PairVerify(req *http.Request, rw *bufio.ReadWriter) (id string, sessionKey []byte, err error) {
	// Request from iPhone
	var plainM1 struct {
		State     byte   `tlv8:"6"`
		PublicKey string `tlv8:"3"`
	}
	if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &plainM1); err != nil {
		return
	}
	if plainM1.State != StateM1 {
		err = newRequestError(plainM1)
		return
	}

	// Generate the key pair
	sessionPublic, sessionPrivate := curve25519.GenerateKeyPair()
	sessionShared, err := curve25519.SharedSecret(sessionPrivate, []byte(plainM1.PublicKey))
	if err != nil {
		return
	}

	encryptKey, err := hkdf.Sha512(
		sessionShared, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info",
	)
	if err != nil {
		return
	}

	b := Append(sessionPublic, s.DeviceID, plainM1.PublicKey)
	signature, err := ed25519.Signature(s.DevicePrivate, b)
	if err != nil {
		return
	}

	// STEP M2. Response to iPhone
	plainM2 := struct {
		Identifier string `tlv8:"1"`
		Signature  string `tlv8:"10"`
	}{
		Identifier: s.DeviceID,
		Signature:  string(signature),
	}
	if b, err = tlv8.Marshal(plainM2); err != nil {
		return
	}

	b, err = chacha20poly1305.Encrypt(encryptKey, "PV-Msg02", b)
	if err != nil {
		return
	}

	cipherM2 := struct {
		State         byte   `tlv8:"6"`
		PublicKey     string `tlv8:"3"`
		EncryptedData string `tlv8:"5"`
	}{
		State:         StateM2,
		PublicKey:     string(sessionPublic),
		EncryptedData: string(b),
	}
	body, err := tlv8.Marshal(cipherM2)
	if err != nil {
		return
	}
	if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
		return
	}

	// STEP M3. Request from iPhone
	if req, err = http.ReadRequest(rw.Reader); err != nil {
		return
	}

	var cipherM3 struct {
		State         byte   `tlv8:"6"`
		EncryptedData string `tlv8:"5"`
	}
	if err = tlv8.UnmarshalReader(req.Body, req.ContentLength, &cipherM3); err != nil {
		return
	}
	if cipherM3.State != StateM3 {
		err = newRequestError(cipherM3)
		return
	}

	b, err = chacha20poly1305.Decrypt(encryptKey, "PV-Msg03", []byte(cipherM3.EncryptedData))
	if err != nil {
		return
	}

	var plainM3 struct {
		Identifier string `tlv8:"1"`
		Signature  string `tlv8:"10"`
	}
	if err = tlv8.Unmarshal(b, &plainM3); err != nil {
		return
	}

	if s.GetClientPublic != nil {
		clientPublic := s.GetClientPublic(plainM3.Identifier)
		if clientPublic == nil {
			err = errors.New("hap: PairVerify with unknown client_id: " + plainM3.Identifier)
			return
		}

		b = Append(plainM1.PublicKey, plainM3.Identifier, sessionPublic)
		if !ed25519.ValidateSignature(clientPublic, b, []byte(plainM3.Signature)) {
			err = errors.New("hap: ValidateSignature")
			return
		}
	}

	// STEP M4. Response to iPhone
	payloadM4 := struct {
		State byte `tlv8:"6"`
	}{
		State: StateM4,
	}
	if body, err = tlv8.Marshal(payloadM4); err != nil {
		return
	}
	if err = WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body); err != nil {
		return
	}

	id = plainM3.Identifier
	sessionKey = sessionShared

	return
}

func WriteResponse(w *bufio.Writer, statusCode int, contentType string, body []byte) error {
	header := fmt.Sprintf(
		"HTTP/1.1 %d %s\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n",
		statusCode, http.StatusText(statusCode), contentType, len(body),
	)
	body = append([]byte(header), body...)
	if _, err := w.Write(body); err != nil {
		return err
	}
	return w.Flush()
}

//func WriteBackoff(rw *bufio.ReadWriter) error {
//	plainM2 := struct {
//		State byte `tlv8:"6"`
//		Error byte `tlv8:"7"`
//	}{
//		State: StateM2,
//		Error: 3, // BackoffError
//	}
//	body, err := tlv8.Marshal(plainM2)
//	if err != nil {
//		return err
//	}
//	return WriteResponse(rw.Writer, http.StatusOK, MimeTLV8, body)
//}
