package vpn

import (
	"encoding/json"
	"github.com/net-byte/vtun/common/config"
	"github.com/net-byte/vtun/tun"
	"github.com/openziti/sdk-golang/ziti"
	"github.com/openziti/zrok/endpoints"
	"github.com/openziti/zrok/environment"
	"github.com/openziti/zrok/sdk/golang/sdk"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"net"
	"time"
)

type FrontendConfig struct {
	IdentityName string
	ShrToken     string
	RequestsChan chan *endpoints.Request
}

type Frontend struct {
	cfg  *FrontendConfig
	ztx  ziti.Context
	conn net.Conn
}

func NewFrontend(cfg *FrontendConfig) (*Frontend, error) {
	env, err := environment.LoadRoot()
	if err != nil {
		return nil, errors.Wrap(err, "error loading environment root")
	}
	zCfgPath, err := env.ZitiIdentityNamed(cfg.IdentityName)
	if err != nil {
		return nil, errors.Wrapf(err, "error getting ziti identity '%v' from environment", cfg.IdentityName)
	}
	zCfg, err := ziti.NewConfigFromFile(zCfgPath)
	if err != nil {
		return nil, errors.Wrap(err, "error loading config")
	}
	zCfg.ConfigTypes = []string{sdk.ZrokProxyConfig}
	zCtx, err := ziti.NewContext(zCfg)
	if err != nil {
		return nil, errors.Wrap(err, "error loading ziti context")
	}

	zConn, err := zCtx.DialWithOptions(cfg.ShrToken, &ziti.DialOptions{ConnectTimeout: 30 * time.Second})
	if err != nil {
		zCtx.Close()
		return nil, errors.Wrap(err, "error connecting to ziti")
	}
	return &Frontend{
		cfg:  cfg,
		ztx:  zCtx,
		conn: zConn,
	}, nil
}

func (f *Frontend) Run() error {
	var cltCfg ClientConfig
	d := json.NewDecoder(f.conn)
	if err := d.Decode(&cltCfg); err != nil {
		return errors.Wrap(err, "error decoding vpn config")
	}
	f.cfg.RequestsChan <- &endpoints.Request{
		Stamp:      time.Now(),
		RemoteAddr: cltCfg.ServerIP,
		Method:     "CONNECTED",
		Path:       cltCfg.Greeting,
	}
	logrus.Info("connected:", cltCfg.Greeting)

	defer func() {
		f.cfg.RequestsChan <- &endpoints.Request{
			Stamp:      time.Now(),
			RemoteAddr: cltCfg.ServerIP,
			Method:     "Disconnected",
		}
	}()

	cfg := config.Config{
		ServerIP:   cltCfg.ServerIP,
		CIDR:       cltCfg.CIDR,
		ServerIPv6: cltCfg.ServerIPv6,
		CIDRv6:     cltCfg.CIDR6,
		MTU:        cltCfg.MTU,
		Verbose:    false,
	}
	iface := tun.CreateTun(cfg)

	logrus.Infof("created tun device: %s", iface.Name())

	go func() {
		defer func() {
			_ = f.conn.Close()
			_ = iface.Close()
		}()

		b := make([]byte, cltCfg.MTU)
		for {
			n, err := f.conn.Read(b)
			if err != nil {
				logrus.WithError(err).Error("error reading from ziti")
				return
			}
			p := packet(b[:n])
			logrus.WithField("packet", p).Trace("received packet from peer")
			_, err = iface.Write(p)
			if err != nil {
				logrus.WithError(err).Error("error writing to device")
				return
			}
		}
	}()

	buf := make([]byte, cltCfg.MTU)
	for {
		n, err := iface.Read(buf)
		if err != nil {
			return errors.Wrap(err, "error reading packet")
		}
		pkt := packet(buf[:n])
		logrus.WithField("packet", pkt).Trace("read packet from tun device")
		_, err = f.conn.Write(pkt)

		if err != nil {
			return errors.Wrap(err, "error sending packet to ziti")
		}
	}
}
