package jess

import (
	"errors"
	"fmt"

	"github.com/safing/jess/tools"
)

const (
	wireStateInit uint8 = iota
	wireStateIdle
	wireStateSendKey
	wireStateAwaitKey
	wireStateSendApply
	wireStatsAwaitApply
)

var (
	// Re-exchange keys every x messages.
	// At 10_000_000 msgs with 1500 bytes per msg, this would result in
	// re-exchanging keys every 15 GB.
	wireReKeyAfterMsgs uint64 = 10_000_000

	requiredWireSessionRequirements = NewRequirements().Remove(SenderAuthentication)
)

// WireSession holds session information specific to communication over a network connection.
type WireSession struct { //nolint:maligned // TODO
	session *Session

	server           bool
	msgNo            uint64
	lastReKeyAtMsgNo uint64

	sendKeyCarryover []byte
	recvKeyCarryover []byte

	// key mgmt state
	eKXSignets     []*kxPair
	eKESignets     []*kePair
	handshakeState uint8
	newKeyMaterial [][]byte
}

// kxPair is key exchange pair.
type kxPair struct {
	tool   tools.ToolLogic
	signet *Signet
	peer   *Signet
}

// kePair is key encapsulation "pair".
type kePair struct {
	tool   tools.ToolLogic
	signet *Signet
	seal   *Seal
}

// initWireSession is called after newSession() to make a wire session from a regular one.
func (s *Session) initWireSession() error {
	// check required requirements
	err := s.toolRequirements.CheckComplianceTo(requiredWireSessionRequirements)
	if err != nil {
		return err
	}

	// check for currently unsupported features
	for _, tool := range s.all {
		switch tool.Info().Purpose {
		case tools.PurposePassDerivation,
			tools.PurposeSigning:
			return fmt.Errorf("wire sessions currently do not support %s", tool.Info().Name)
		}
	}

	// check for static pre shared keys
	err = s.envelope.LoopSecrets(SignetSchemeKey, func(signet *Signet) error {
		return errors.New("wire sessions currently do not support pre-shared keys")
	})
	if err != nil {
		return err
	}

	s.wire = &WireSession{
		session: s,
	}

	return nil
}

// Server marks a wire session as being in the role of the server, rather than the client.
func (s *Session) Server() {
	if s.wire != nil {
		s.wire.server = true
	}
}

// reKeyNeeded returns whether rekeying is needed.
func (w *WireSession) reKeyNeeded() bool {
	return w.msgNo-w.lastReKeyAtMsgNo > wireReKeyAfterMsgs
}