//go:build with_gvisor

package openconnect

import (
	"net/netip"

	"github.com/sagernet/sing-tun/gtcpip/header"
	"github.com/sagernet/sing/common/buf"
	E "github.com/sagernet/sing/common/exceptions"
)

var _ Device = (*systemStackDevice)(nil)

type systemStackDevice struct {
	*systemDevice
	stackDevice *stackDevice
}

func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {
	system, err := newSystemDevice(options)
	if err != nil {
		return nil, err
	}
	stackOptions := options
	stackOptions.System = false
	stackOptions.Name = system.options.Name
	stackOptions.ExcludeInterface = []string{system.options.Name}
	stackDevice, err := newStackDevice(stackOptions)
	if err != nil {
		system.Close()
		return nil, err
	}
	return &systemStackDevice{
		systemDevice: system,
		stackDevice:  stackDevice,
	}, nil
}

func (d *systemStackDevice) SetPacketWriter(writer PacketWriter) {
	d.systemDevice.SetPacketWriter(writer)
	d.stackDevice.SetPacketWriter(writer)
}

func (d *systemStackDevice) Start() error {
	err := d.stackDevice.Start()
	if err != nil {
		return err
	}
	err = d.systemDevice.Start()
	return err
}

func (d *systemStackDevice) UpdateConfiguration(configuration Configuration) error {
	err := d.systemDevice.UpdateConfiguration(configuration)
	if err != nil {
		return err
	}
	return d.stackDevice.UpdateConfiguration(configuration)
}

func (d *systemStackDevice) WriteInboundBuffers(packetBuffers []*buf.Buffer) error {
	return d.systemDevice.processInboundBuffers(packetBuffers, d.writeBuffers)
}

func (d *systemStackDevice) writeBuffers(packetBuffers []*buf.Buffer) error {
	addresses := d.systemDevice.configurationAddresses()
	runStart := 0
	runUsesSystemDevice := false
	var writeErr error
	for i, packetBuffer := range packetBuffers {
		destination := packetDestination(packetBuffer.Bytes())
		useSystemDevice := false
		for _, prefix := range addresses {
			if prefix.Contains(destination) {
				useSystemDevice = true
				break
			}
		}
		if i > runStart && useSystemDevice != runUsesSystemDevice {
			var err error
			if runUsesSystemDevice {
				err = d.systemDevice.writeBuffers(packetBuffers[runStart:i])
			} else {
				err = d.stackDevice.writeBuffers(packetBuffers[runStart:i])
			}
			writeErr = E.Errors(writeErr, err)
			runStart = i
		}
		if i == runStart {
			runUsesSystemDevice = useSystemDevice
		}
	}
	if runStart == len(packetBuffers) {
		return writeErr
	}
	if runUsesSystemDevice {
		return E.Errors(writeErr, d.systemDevice.writeBuffers(packetBuffers[runStart:]))
	}
	return E.Errors(writeErr, d.stackDevice.writeBuffers(packetBuffers[runStart:]))
}

func packetDestination(packet []byte) netip.Addr {
	switch header.IPVersion(packet) {
	case header.IPv4Version:
		return header.IPv4(packet).DestinationAddr()
	case header.IPv6Version:
		return header.IPv6(packet).DestinationAddr()
	default:
		return netip.Addr{}
	}
}

func (d *systemStackDevice) Close() error {
	return E.Errors(d.stackDevice.Close(), d.systemDevice.Close())
}
