package archival

//
// Saves QUIC events.
//

import (
	"context"
	"crypto/tls"
	"net"
	"time"

	"github.com/lucas-clemente/quic-go"
	"github.com/ooni/probe-cli/v3/internal/model"
	"github.com/ooni/probe-cli/v3/internal/netxlite"
)

// WriteTo performs WriteTo with the given pconn and saves the
// operation's results inside the saver.
func (s *Saver) WriteTo(pconn model.UDPLikeConn, buf []byte, addr net.Addr) (int, error) {
	started := time.Now()
	count, err := pconn.WriteTo(buf, addr)
	s.appendNetworkEvent(&NetworkEvent{
		Count:      count,
		Failure:    err,
		Finished:   time.Now(),
		Network:    addr.Network(),
		Operation:  netxlite.WriteToOperation,
		RemoteAddr: addr.String(),
		Started:    started,
	})
	return count, err
}

// ReadFrom performs ReadFrom with the given pconn and saves the
// operation's results inside the saver.
func (s *Saver) ReadFrom(pconn model.UDPLikeConn, buf []byte) (int, net.Addr, error) {
	started := time.Now()
	count, addr, err := pconn.ReadFrom(buf)
	s.appendNetworkEvent(&NetworkEvent{
		Count:      count,
		Failure:    err,
		Finished:   time.Now(),
		Network:    "udp", // must be always set even on failure
		Operation:  netxlite.ReadFromOperation,
		RemoteAddr: s.safeAddrString(addr),
		Started:    started,
	})
	return count, addr, err
}

func (s *Saver) safeAddrString(addr net.Addr) (out string) {
	if addr != nil {
		out = addr.String()
	}
	return
}

// QUICDialContext dials a QUIC session using the given dialer
// and saves the results inside of the saver.
func (s *Saver) QUICDialContext(ctx context.Context, dialer model.QUICDialer,
	network, address string, tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlySession, error) {
	started := time.Now()
	var state tls.ConnectionState
	sess, err := dialer.DialContext(ctx, network, address, tlsConfig, quicConfig)
	if err == nil {
		select {
		case <-sess.HandshakeComplete().Done():
			state = sess.ConnectionState().TLS.ConnectionState
		case <-ctx.Done():
			sess, err = nil, ctx.Err()
		}
	}
	s.appendQUICHandshake(&QUICTLSHandshakeEvent{
		ALPN:            tlsConfig.NextProtos,
		CipherSuite:     netxlite.TLSCipherSuiteString(state.CipherSuite),
		Failure:         err,
		Finished:        time.Now(),
		NegotiatedProto: state.NegotiatedProtocol,
		Network:         "quic",
		PeerCerts:       s.tlsPeerCerts(err, &state),
		RemoteAddr:      address,
		SNI:             tlsConfig.ServerName,
		SkipVerify:      tlsConfig.InsecureSkipVerify,
		Started:         started,
		TLSVersion:      netxlite.TLSVersionString(state.Version),
	})
	return sess, err
}

func (s *Saver) appendQUICHandshake(ev *QUICTLSHandshakeEvent) {
	s.mu.Lock()
	s.trace.QUICHandshake = append(s.trace.QUICHandshake, ev)
	s.mu.Unlock()
}
