package quic

import (
	"bytes"
	"fmt"
	"io"

	capnp "zombiezen.com/go/capnproto2"
)

// protocolSignature is a custom protocol signature to ensure that whoever performs a handshake does not write data
// before writing the metadata.
var protocolSignature = []byte{0x0A, 0x36, 0xCD, 0x12, 0xA1, 0x3E}

const protocolVersionLength = 2

type protocolVersion string

const (
	protocolV1 protocolVersion = "01"
)

// ReadConnectRequestData reads the handshake data from a QUIC stream.
func ReadConnectRequestData(stream io.Reader) (*ConnectRequest, error) {
	if err := verifySignature(stream); err != nil {
		return nil, err
	}

	// This is a NO-OP for now. We could cause a branching if we wanted to use multiple versions.
	if _, err := readVersion(stream); err != nil {
		return nil, err
	}

	msg, err := capnp.NewDecoder(stream).Decode()
	if err != nil {
		return nil, err
	}

	r := &ConnectRequest{}
	if err := r.fromPogs(msg); err != nil {
		return nil, err
	}
	return r, nil
}

// WriteConnectRequestData writes requestMeta to a stream.
func WriteConnectRequestData(stream io.Writer, dest string, connectionType ConnectionType, metadata ...Metadata) error {
	connectRequest := &ConnectRequest{
		Dest:     dest,
		Type:     connectionType,
		Metadata: metadata,
	}

	msg, err := connectRequest.toPogs()
	if err != nil {
		return err
	}

	if err := writePreamble(stream); err != nil {
		return err
	}
	return capnp.NewEncoder(stream).Encode(msg)
}

// ReadConnectResponseData reads the response to a RequestMeta in a stream.
func ReadConnectResponseData(stream io.Reader) (*ConnectResponse, error) {
	if err := verifySignature(stream); err != nil {
		return nil, err
	}

	// This is a NO-OP for now. We could cause a branching if we wanted to use multiple versions.
	if _, err := readVersion(stream); err != nil {
		return nil, err
	}

	msg, err := capnp.NewDecoder(stream).Decode()
	if err != nil {
		return nil, err
	}

	r := &ConnectResponse{}
	if err := r.fromPogs(msg); err != nil {
		return nil, err
	}
	return r, nil
}

// WriteConnectResponseData writes response to a QUIC stream.
func WriteConnectResponseData(stream io.Writer, respErr error, metadata ...Metadata) error {
	var connectResponse *ConnectResponse
	if respErr != nil {
		connectResponse = &ConnectResponse{
			Error: respErr.Error(),
		}
	} else {
		connectResponse = &ConnectResponse{
			Metadata: metadata,
		}
	}

	msg, err := connectResponse.toPogs()
	if err != nil {
		return err
	}

	if err := writePreamble(stream); err != nil {
		return err
	}
	return capnp.NewEncoder(stream).Encode(msg)
}

func writePreamble(stream io.Writer) error {
	if err := writeSignature(stream); err != nil {
		return err
	}

	return writeVersion(stream)
}

func writeVersion(stream io.Writer) error {
	_, err := stream.Write([]byte(protocolV1)[:protocolVersionLength])
	return err
}

func readVersion(stream io.Reader) (string, error) {
	version := make([]byte, protocolVersionLength)
	_, err := stream.Read(version)
	return string(version), err
}

func writeSignature(stream io.Writer) error {
	_, err := stream.Write(protocolSignature)
	return err
}

func verifySignature(stream io.Reader) error {
	signature := make([]byte, len(protocolSignature))
	if _, err := io.ReadFull(stream, signature); err != nil {
		return err
	}

	if !bytes.Equal(signature[0:], protocolSignature) {
		return fmt.Errorf("Wrong signature: %v", signature)
	}

	return nil
}
