package gvisorstack

import (
	"context"
	"fmt"

	"gvisor.dev/gvisor/pkg/tcpip"
	"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
	"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
	"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
	"gvisor.dev/gvisor/pkg/tcpip/stack"
	"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
	"gvisor.dev/gvisor/pkg/tcpip/transport/udp"

	"github.com/v2fly/v2ray-core/v5/common/packetswitch"
)

//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen

type WrappedStack struct {
	config *Config
	ctx    context.Context
	stack  *stack.Stack

	stackTCPListeners []*gonet.TCPListener
}

func NewStack(ctx context.Context, config *Config) (*WrappedStack, error) {
	return &WrappedStack{
		config: config,
		ctx:    ctx,
	}, nil
}

func (w *WrappedStack) CreateStackFromNetworkLayerDevice(packetSwitchDevice packetswitch.NetworkLayerDevice) error {
	// Validate
	if w == nil || w.config == nil {
		return fmt.Errorf("no config")
	}

	// Determine MTU from config (0 means unspecified)
	mtu := int(w.config.GetMtu())

	// Create adaptor that implements stack.LinkEndpoint
	adaptor := NewNetworkLayerDeviceToGvisorLinkEndpointAdaptor(w.ctx, mtu, packetSwitchDevice)

	// Create stack using adaptor as link endpoint
	s, err := w.createStack(adaptor)
	if err != nil {
		// cleanup adaptor on error
		adaptor.Close()
		return fmt.Errorf("failed to create gvisor stack: %v", err)
	}

	// When the adaptor is closed, close the stack as well.
	adaptor.SetOnCloseAction(func() {
		if s != nil {
			s.Close()
		}
	})

	w.stack = s

	if err := w.ApplyListeners(); err != nil {
		return fmt.Errorf("failed to apply listeners: %v", err)
	}
	return nil
}

func (w *WrappedStack) createStack(linkedEndpoint stack.LinkEndpoint) (*stack.Stack, error) {
	// Machine Generated
	if w == nil || w.config == nil {
		return nil, fmt.Errorf("no config")
	}

	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{
			ipv4.NewProtocol,
			ipv6.NewProtocol,
		},
		TransportProtocols: []stack.TransportProtocolFactory{
			tcp.NewProtocol,
			udp.NewProtocol,
			icmp.NewProtocol4,
			icmp.NewProtocol6,
		},
	})

	nicID := s.NextNICID()

	// Create NIC
	if err := s.CreateNICWithOptions(nicID, linkedEndpoint, stack.NICOptions{Disabled: false, QDisc: nil}); err != nil {
		return nil, fmt.Errorf("failed to create NIC: %v", err)
	}

	// Add protocol addresses
	for _, ip := range w.config.Ips {
		tcpIPAddr := tcpip.AddrFromSlice(ip.Ip)
		protocolAddress := tcpip.ProtocolAddress{
			AddressWithPrefix: tcpip.AddressWithPrefix{
				Address:   tcpIPAddr,
				PrefixLen: int(ip.Prefix),
			},
		}

		switch tcpIPAddr.Len() {
		case 4:
			protocolAddress.Protocol = ipv4.ProtocolNumber
		case 16:
			protocolAddress.Protocol = ipv6.ProtocolNumber
		default:
			return nil, fmt.Errorf("invalid IP address length: %d", tcpIPAddr.Len())
		}

		if err := s.AddProtocolAddress(nicID, protocolAddress, stack.AddressProperties{}); err != nil {
			return nil, fmt.Errorf("failed to add protocol address: %v", err)
		}
	}

	// Set route table
	s.SetRouteTable(func() (table []tcpip.Route) {
		for _, cidrs := range w.config.Routes {
			subnet := tcpip.AddressWithPrefix{
				Address:   tcpip.AddrFromSlice(cidrs.Ip),
				PrefixLen: int(cidrs.Prefix),
			}.Subnet()
			route := tcpip.Route{
				Destination: subnet,
				NIC:         nicID,
			}
			table = append(table, route)
		}
		return
	}())

	// Promiscuous & spoofing
	if err := s.SetPromiscuousMode(nicID, w.config.EnablePromiscuousMode); err != nil {
		return nil, fmt.Errorf("failed to set promiscuous mode: %v", err)
	}
	if err := s.SetSpoofing(nicID, w.config.EnableSpoofing); err != nil {
		return nil, fmt.Errorf("failed to set spoofing: %v", err)
	}

	// Apply socket buffer sizes if provided
	if w.config.SocketSettings != nil {
		if size := w.config.SocketSettings.TxBufSize; size != 0 {
			sendBufferSizeRangeOption := tcpip.TCPSendBufferSizeRangeOption{Min: tcp.MinBufferSize, Default: int(size), Max: tcp.MaxBufferSize}
			if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sendBufferSizeRangeOption); err != nil {
				return nil, fmt.Errorf("failed to set tcp send buffer size: %v", err)
			}
		}

		if size := w.config.SocketSettings.RxBufSize; size != 0 {
			receiveBufferSizeRangeOption := tcpip.TCPReceiveBufferSizeRangeOption{Min: tcp.MinBufferSize, Default: int(size), Max: tcp.MaxBufferSize}
			if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &receiveBufferSizeRangeOption); err != nil {
				return nil, fmt.Errorf("failed to set tcp receive buffer size: %v", err)
			}
		}
	}

	return s, nil
}

func (w *WrappedStack) Close() error {
	if w == nil || w.stack == nil {
		return nil
	}
	for _, l := range w.stackTCPListeners {
		l.Close()
	}
	w.stackTCPListeners = nil
	w.stack.Close()
	w.stack = nil
	return nil
}
