package connection

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"io"
	"io/ioutil"
	"math/big"

	"github.com/lucas-clemente/quic-go"
	"gitlab.wtotem.net/webtotem/logger"
)

const (
	AcceptReadFrameTypeError = iota
	AcceptReadHeaderLengthError
	AcceptReadBodyLengthError
	AcceptMaxHeaderLengthReachedError
	AcceptUnknownFrameTypeError
	AcceptReadHeaderError
	AcceptReadBodyError
)

var log logger.Logger

func init() {
	log = logger.NewLogger(&logger.LoggerInfo{
		CallerType: logger.LibCaller,
		Layer:      logger.TransportLayer,
		Name:       "transport",
		ModuleType: logger.OtherType,
	})
}

type connectionLayerImpl struct {
	t TransportLayer
}

// byteReader - interface for reading bit by bit. Need in function quicvarint.Read
type byteReader interface {
	io.ByteReader
	io.Reader
}

// byteReaderImpl - implementation of byteReader interface
type byteReaderImpl struct{ io.Reader }

func (br *byteReaderImpl) ReadByte() (byte, error) {
	b := make([]byte, 1)
	if _, err := br.Reader.Read(b); err != nil {
		return 0, err
	}
	return b[0], nil
}

// acceptIncomingUniStreams - functions for continuously accepting new unidirectional streams
func (c *connectionLayerImpl) acceptIncomingUniStreams(session *ConnectionSession) {
	fName := "acceptIncomingUniStreams"
	ctx := session.GetContext()
	for {
		str, err := NewIncomingUniStream(ctx, session.conn)
		if err != nil {
			log.Errorf("[%s]: Error while accepting new stream. Error: %v", fName, err)
			session.TransportSession.OnClose(err)
			return
		}

		// Read header part from stream

		header, err := readHeader(str)
		if err != nil {
			log.Errorf("[%s]: Error while reading frame header. Error: %v", fName, err)
			session.TransportSession.OnClose(err)
			return
		}

		// Header was read from stream
		// Call OnStreamAccept callback

		go session.TransportSession.OnStreamAccept(header, str)
	}
}

func (c *connectionLayerImpl) processIncomingStream(ctx context.Context, str FrameStream, session *ConnectionSession) {
	fName := "processIncomingStream"
	defer func() {
		str.Close()
		// TODO: Fix reading last EOF byte
		_, err := ioutil.ReadAll(str)
		if err != nil {
			log.Errorf("[%s]: Error while reading EOF. Error: %v", fName, err)
		}
	}()

	// Read header part from stream

	header, err := readHeader(str)
	if err != nil {
		frHeader := session.TransportSyncReqReceiver.OnError(err)
		writeHeader(str, frHeader)
		log.Errorf("[%s]: Error while reading frame header. Error: %v", fName, err)
		return
	}

	// Header was read from stream
	// Begin read body from stream

	buf := make([]byte, header.BodyLength)
	_, err = io.ReadFull(str, buf)
	if err != nil {
		frHeader := session.TransportSyncReqReceiver.OnError(err)
		writeHeader(str, frHeader)
		log.Errorf("[%s]: Error while reading frame body. Error: %v", fName, err)
		return
	}

	// Frame body was read. Call request callback
	respHeader, respBody := session.TransportSyncReqReceiver.OnSyncRequest(header, buf)
	err = writeHeader(str, respHeader)
	if err != nil {
		log.Errorf("[%s]: Error while sending frame header. Error: %v", fName, err)
		return
	}
	_, err = str.Write(respBody)
	if err != nil {
		log.Errorf("[%s]: Error while sending frame body. Error: %v", fName, err)
		return
	}
}

// acceptIncomingStreams - functions for continuously accepting new streams
func (c *connectionLayerImpl) acceptIncomingStreams(session *ConnectionSession) {
	fName := "acceptIncomingStreams"
	ctx := session.GetContext()
	for {
		str, err := NewIncomingStream(ctx, session.conn)
		if err != nil {
			log.Errorf("[%s]: Failed to receive stream: %v", fName, err)
			return
		}
		go c.processIncomingStream(ctx, str, session)
	}
}

// OpenSession - method for opening new session with transport server
func (c *connectionLayerImpl) OpenSession(addr string) (Session, error) {
	fName := "OpenSession"
	tlsConf := &tls.Config{
		InsecureSkipVerify: true,
		NextProtos:         []string{ProtoName},
	}
	QUICSess, err := quic.DialAddr(addr, tlsConf, &quic.Config{
		KeepAlive:                  true,
		MaxIdleTimeout:             0,
		InitialStreamReceiveWindow: 10 * 1024,
	})

	if err != nil {
		log.Errorf("[%s]: Error while dialing with QUIC server. Error: %v", fName, err)
		return nil, err
	}

	log.Infof("[%s]: Session accepted", fName)

	session := NewSession(QUICSess)

	// Call OnNewSession for creating interface with needed callbacks
	sessEvents, err := c.t.OnNewSession(session)
	if err != nil {
		log.Errorf("[%s]: Error while creating session events object. Error: %v", fName, err)
		return nil, err
	}

	session.TransportSession = sessEvents

	go c.acceptIncomingUniStreams(session)

	return session, nil
}

// Setup a bare-bones TLS config for the server
func generateTLSConfig() *tls.Config {
	key, err := rsa.GenerateKey(rand.Reader, 1024)
	if err != nil {
		panic(err)
	}
	template := x509.Certificate{SerialNumber: big.NewInt(1)}
	certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
	if err != nil {
		panic(err)
	}
	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})

	tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
	if err != nil {
		panic(err)
	}
	return &tls.Config{
		Certificates: []tls.Certificate{tlsCert},
		NextProtos:   []string{ProtoName},
	}
}

// ListenSession - method for continuously accepting new sessions
func (c *connectionLayerImpl) ListenSession(ctx context.Context, addr string) error {
	fName := "ListenSession"

	var ok bool

	listener, err := quic.ListenAddr(addr, generateTLSConfig(), &quic.Config{
		KeepAlive:                  true,
		MaxIdleTimeout:             0,
		InitialStreamReceiveWindow: 10 * 1024,
	})

	if err != nil {
		log.Errorf("[%s]: Error while QUIC server listening. Error: %v", fName, err)
		return err
	}
	defer listener.Close()

	log.Infof("[%s]: QUIC server successfully created", fName)

	for {
		QUICSess, err := listener.Accept(ctx)
		if err != nil {
			if ctx.Err() != nil {
				log.Warnf("[%s]: Context was canceled", fName)
				return nil
			}
			log.Errorf("[%s]: Error while accepting new QUIC session. Error: %v", fName, err)
			return err
		}

		log.Infof("[%s]: Session accepted", fName)

		session := NewSession(QUICSess)

		// Call OnNewSession for creating interface with needed callbacks
		sessEvents, err := c.t.OnNewSession(session)
		if err != nil {
			log.Errorf("[%s]: Error while creating session events object. Error: %v", fName, err)
			QUICSess.CloseWithError(quic.ApplicationErrorCode(quic.InternalError), fmt.Sprintf("Error while creating session. Error: %v", err))
			continue
		}

		session.TransportSession = sessEvents

		session.TransportSyncReqReceiver, ok = sessEvents.(SyncRequestReceiver)
		if !ok {
			log.Errorf("[%s]: Error! Transport layer session must implement \"SyncRequestReceiver\" interface", fName)
			QUICSess.CloseWithError(quic.ApplicationErrorCode(quic.InternalError), "Error! Transport layer session must implement \"SyncRequestReceiver\" interface")
			continue
		}

		go c.acceptIncomingUniStreams(session)
		go c.acceptIncomingStreams(session)
	}
}

// NewConnectionLayer - function for creating connection layer
func NewConnectionLayer(transportLayer TransportLayer) ConnectionLayer {
	return &connectionLayerImpl{
		t: transportLayer,
	}
}
