package hap

import (
	"bufio"
	"encoding/binary"
	"encoding/json"
	"errors"
	"io"
	"net"
	"sync"
	"time"

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

type Conn struct {
	conn net.Conn
	rw   *bufio.ReadWriter
	wmu  sync.Mutex

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

	//ClientID  string
	SharedKey []byte

	recv int
	send int
}

func (c *Conn) MarshalJSON() ([]byte, error) {
	conn := core.Connection{
		ID:         core.ID(c),
		FormatName: "homekit",
		Protocol:   "hap",
		RemoteAddr: c.conn.RemoteAddr().String(),
		Recv:       c.recv,
		Send:       c.send,
	}
	return json.Marshal(conn)
}

func NewConn(conn net.Conn, rw *bufio.ReadWriter, sharedKey []byte, isClient bool) (*Conn, error) {
	key1, err := hkdf.Sha512(sharedKey, "Control-Salt", "Control-Read-Encryption-Key")
	if err != nil {
		return nil, err
	}

	key2, err := hkdf.Sha512(sharedKey, "Control-Salt", "Control-Write-Encryption-Key")
	if err != nil {
		return nil, err
	}

	c := &Conn{
		conn: conn,
		rw:   rw,

		SharedKey: sharedKey,
	}

	if isClient {
		c.encryptKey, c.decryptKey = key2, key1
	} else {
		c.encryptKey, c.decryptKey = key1, key2
	}

	return c, nil
}

const (
	// packetSizeMax is the max length of encrypted packets
	packetSizeMax = 0x400

	VerifySize = 2
	NonceSize  = 8
	Overhead   = 16 // chacha20poly1305.Overhead
)

func (c *Conn) Read(b []byte) (n int, err error) {
	if cap(b) < packetSizeMax {
		return 0, errors.New("hap: read buffer is too small")
	}

	verify := make([]byte, VerifySize) // verify = plain message size
	if _, err = io.ReadFull(c.rw, verify); err != nil {
		return
	}

	n = int(binary.LittleEndian.Uint16(verify))

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

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

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

	c.recv += n
	return
}

func (c *Conn) Write(b []byte) (n int, err error) {
	c.wmu.Lock()
	defer c.wmu.Unlock()

	buf := make([]byte, 0, packetSizeMax+Overhead)
	nonce := make([]byte, NonceSize)
	verify := make([]byte, VerifySize)

	for len(b) > 0 {
		size := len(b)
		if size > packetSizeMax {
			size = packetSizeMax
		}

		binary.LittleEndian.PutUint16(verify, uint16(size))
		if _, err = c.rw.Write(verify); err != nil {
			return
		}

		binary.LittleEndian.PutUint64(nonce, c.encryptCnt)
		c.encryptCnt++

		_, err = chacha20poly1305.EncryptAndSeal(c.encryptKey, buf, nonce, b[:size], verify)
		if err != nil {
			return
		}

		if _, err = c.rw.Write(buf[:size+Overhead]); err != nil {
			return
		}

		b = b[size:]
		n += size
	}

	err = c.rw.Flush()

	c.send += n
	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)
}
