// Package hds - HomeKit Data Stream
package hds

import (
	"bufio"
	"encoding/binary"
	"io"
	"net"
	"time"

	"github.com/AlexxIT/go2rtc/pkg/hap/chacha20poly1305"
	"github.com/AlexxIT/go2rtc/pkg/hap/hkdf"
	"github.com/AlexxIT/go2rtc/pkg/hap/secure"
)

func Client(conn net.Conn, key []byte, salt string, controller bool) (*Conn, error) {
	writeKey, err := hkdf.Sha512(key, salt, "HDS-Write-Encryption-Key")
	if err != nil {
		return nil, err
	}

	readKey, err := hkdf.Sha512(key, salt, "HDS-Read-Encryption-Key")
	if err != nil {
		return nil, err
	}

	c := &Conn{
		conn: conn,
		rd:   bufio.NewReaderSize(conn, 32*1024),
		wr:   bufio.NewWriterSize(conn, 32*1024),
	}

	if controller {
		c.decryptKey, c.encryptKey = readKey, writeKey
	} else {
		c.decryptKey, c.encryptKey = writeKey, readKey
	}

	return c, nil
}

type Conn struct {
	conn net.Conn

	rd *bufio.Reader
	wr *bufio.Writer

	decryptKey []byte
	encryptKey []byte
	decryptCnt uint64
	encryptCnt uint64
}

func (c *Conn) Read(p []byte) (n int, err error) {
	verify := make([]byte, 4)
	if _, err = io.ReadFull(c.rd, verify); err != nil {
		return
	}

	n = int(binary.BigEndian.Uint32(verify) & 0xFFFFFF)

	ciphertext := make([]byte, n+secure.Overhead)
	if _, err = io.ReadFull(c.rd, ciphertext); err != nil {
		return
	}

	nonce := make([]byte, secure.NonceSize)
	binary.LittleEndian.PutUint64(nonce, c.decryptCnt)
	c.decryptCnt++

	_, err = chacha20poly1305.DecryptAndVerify(c.decryptKey, p[:0], nonce, ciphertext, verify)
	return
}

func (c *Conn) Write(b []byte) (n int, err error) {
	n = len(b)

	verify := make([]byte, 4)
	binary.BigEndian.PutUint32(verify, 0x01000000|uint32(n))
	if _, err = c.wr.Write(verify); err != nil {
		return
	}

	nonce := make([]byte, secure.NonceSize)
	binary.LittleEndian.PutUint64(nonce, c.encryptCnt)
	c.encryptCnt++

	buf := make([]byte, n+secure.Overhead)
	if _, err = chacha20poly1305.EncryptAndSeal(c.encryptKey, buf[:0], nonce, b, verify); err != nil {
		return
	}

	if _, err = c.wr.Write(buf); err != nil {
		return
	}

	err = c.wr.Flush()
	return
}

func (c *Conn) Close() error {
	return c.conn.Close()
}

func (c *Conn) LocalAddr() net.Addr {
	return c.conn.LocalAddr()
}

func (c *Conn) RemoteAddr() net.Addr {
	return c.conn.RemoteAddr()
}

func (c *Conn) SetDeadline(t time.Time) error {
	return c.conn.SetDeadline(t)
}

func (c *Conn) SetReadDeadline(t time.Time) error {
	return c.conn.SetReadDeadline(t)
}

func (c *Conn) SetWriteDeadline(t time.Time) error {
	return c.conn.SetWriteDeadline(t)
}
