package main

import (
	"fmt"
	"log"
	"net"
	"os"
	"time"

	"git.sagar.ch/sagar/softether-go/pkg/client"
	"git.sagar.ch/sagar/softether-go/pkg/dhcp"
	"git.sagar.ch/sagar/softether-go/pkg/netcfg"
	"git.sagar.ch/sagar/softether-go/pkg/tap"
)

var errShutdown = fmt.Errorf("shutdown requested")

func runSession(cfg client.Config, dev *tap.Device, mac net.HardwareAddr, opts netcfg.Options, sig chan os.Signal) error {
	log.Printf("connecting to %s:%d hub=%s user=%s", cfg.Host, cfg.Port, cfg.Hub, cfg.Username)

	sess, err := client.Connect(cfg)
	if err != nil {
		return fmt.Errorf("connect: %w", err)
	}
	log.Printf("connected: session=%s connection=%s", sess.SessionName, sess.ConnectionName)

	tunnel := client.NewTunnel(sess)
	tunnel.StartKeepalive()
	defer tunnel.Close()

	// DHCP client feeds on server frames if enabled
	var dhcpClient *dhcp.Client
	var onFrame client.FrameHandler
	if opts.DHCP {
		dhcpClient = dhcp.NewClient(mac)
		onFrame = func(frame []byte) error {
			dhcpClient.FeedFrame(frame)
			return nil
		}
	}

	// Start bridge in background
	bridgeErr := make(chan error, 1)
	go func() {
		bridgeErr <- tunnel.Bridge(dev.Read, dev.Write, onFrame)
	}()

	// Run DHCP and configure network if enabled
	if opts.DHCP {
		lease, err := runDHCP(dhcpClient, tunnel)
		if err != nil {
			return fmt.Errorf("dhcp: %w", err)
		}

		cleanup, err := netcfg.ConfigureTAP(dev.Name, lease, opts.AcceptDefaultGW, opts.AcceptStaticRoutes, opts.AcceptDNS)
		if err != nil {
			return fmt.Errorf("configure tap: %w", err)
		}
		defer cleanup()

		if opts.PolicyRouteTable > 0 && lease.Gateway != nil {
			defer netcfg.ConfigurePolicyRoute(dev.Name, lease, opts.PolicyRouteTable)()
		}
	}

	select {
	case err := <-bridgeErr:
		return err
	case <-sig:
		return errShutdown
	}
}

func runDHCP(dc *dhcp.Client, tunnel *client.Tunnel) (*dhcp.Lease, error) {
	log.Println("starting DHCP exchange...")

	lease, err := dc.Run(func(frame []byte) error {
		return tunnel.WriteFrames([][]byte{frame})
	}, 10*time.Second)
	if err != nil {
		return nil, err
	}

	ones, _ := lease.SubnetMask.Size()
	log.Printf("dhcp lease: ip=%s/%d gw=%s dns=%v ttl=%v",
		lease.ClientIP, ones, lease.Gateway, lease.DNS, lease.LeaseTime)
	for _, r := range lease.Routes {
		log.Printf("dhcp route: %s via %s", r.Dest.String(), r.Gateway)
	}
	return lease, nil
}
