// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause

package libtailscale

import (
	"errors"
	"fmt"
	"log"
	"net"
	"net/netip"
	"runtime/debug"
	"strings"
	"syscall"

	"github.com/tailscale/wireguard-go/tun"
	"inet.af/netaddr"
	"tailscale.com/net/dns"
	"tailscale.com/net/netmon"
	"tailscale.com/util/dnsname"
	"tailscale.com/wgengine/router"
)

// errVPNNotPrepared is used when VPNService.Builder.establish returns
// null, either because the VPNService is not yet prepared or because
// VPN status was revoked.
var errVPNNotPrepared = errors.New("VPN service not prepared or was revoked")

// errMultipleUsers is used when we get a "INTERACT_ACROSS_USERS" error, which
// happens due to a bug in Android. See:
//
//	https://github.com/tailscale/tailscale/issues/2180
var errMultipleUsers = errors.New("VPN cannot be created on this device due to an Android bug with multiple users")

// VpnService contains the IPNService class from Android, the file descriptor, and whether the descriptor has been detached.
type VpnService struct {
	service    IPNService
	fd         int32
	fdDetached bool
}

var vpnService = &VpnService{}

// Report interfaces in the device in net.Interface format.
func (a *App) getInterfaces() ([]netmon.Interface, error) {
	var ifaces []netmon.Interface

	ifaceString, err := a.appCtx.GetInterfacesAsString()
	if err != nil {
		return ifaces, err
	}

	for _, iface := range strings.Split(ifaceString, "\n") {
		// Example of the strings we're processing:
		// wlan0 30 1500 true true false false true | fe80::2f60:2c82:4163:8389%wlan0/64 10.1.10.131/24
		// r_rmnet_data0 21 1500 true false false false false | fe80::9318:6093:d1ad:ba7f%r_rmnet_data0/64
		// mnet_data2 12 1500 true false false false false | fe80::3c8c:44dc:46a9:9907%rmnet_data2/64

		if strings.TrimSpace(iface) == "" {
			continue
		}

		fields := strings.Split(iface, "|")
		if len(fields) != 2 {
			log.Printf("getInterfaces: unable to split %q", iface)
			continue
		}

		var name string
		var index, mtu int
		var up, broadcast, loopback, pointToPoint, multicast bool
		_, err := fmt.Sscanf(fields[0], "%s %d %d %t %t %t %t %t",
			&name, &index, &mtu, &up, &broadcast, &loopback, &pointToPoint, &multicast)
		if err != nil {
			log.Printf("getInterfaces: unable to parse %q: %v", iface, err)
			continue
		}

		newIf := netmon.Interface{
			Interface: &net.Interface{
				Name:  name,
				Index: index,
				MTU:   mtu,
			},
			AltAddrs: []net.Addr{}, // non-nil to avoid Go using netlink
		}
		if up {
			newIf.Flags |= net.FlagUp
		}
		if broadcast {
			newIf.Flags |= net.FlagBroadcast
		}
		if loopback {
			newIf.Flags |= net.FlagLoopback
		}
		if pointToPoint {
			newIf.Flags |= net.FlagPointToPoint
		}
		if multicast {
			newIf.Flags |= net.FlagMulticast
		}

		addrs := strings.Trim(fields[1], " \n")
		for _, addr := range strings.Split(addrs, " ") {
			ip, err := netaddr.ParseIPPrefix(addr)
			if err == nil {
				newIf.AltAddrs = append(newIf.AltAddrs, ip.IPNet())
			}
		}

		ifaces = append(ifaces, newIf)
	}

	return ifaces, nil
}

// googleDNSServers are used on ChromeOS, where an empty VpnBuilder DNS setting results
// in erasing the platform DNS servers. The developer docs say this is not supposed to happen,
// but nonetheless it does.
var googleDNSServers = []netip.Addr{
	netip.MustParseAddr("8.8.8.8"),
	netip.MustParseAddr("8.8.4.4"),
	netip.MustParseAddr("2001:4860:4860::8888"),
	netip.MustParseAddr("2001:4860:4860::8844"),
}

func (b *backend) updateTUN(rcfg *router.Config, dcfg *dns.OSConfig) error {
	b.logger.Logf("updateTUN: changed")
	defer b.logger.Logf("updateTUN: finished")

	// Close previous tunnel(s).
	// This is necessary for ChromeOS, native Android devices
	// seem to handle seamless handover between tunnels correctly.
	//
	// TODO(eliasnaur): If seamless handover becomes a desirable feature, skip
	// the closing on ChromeOS.
	b.logger.Logf("updateTUN: closing old TUNs")
	b.CloseTUNs()
	b.logger.Logf("updateTUN: closed old TUNs")

	if len(rcfg.LocalAddrs) == 0 {
		return nil
	}
	builder := vpnService.service.NewBuilder()
	b.logger.Logf("updateTUN: got new builder")

	if err := builder.SetMTU(defaultMTU); err != nil {
		return err
	}
	b.logger.Logf("updateTUN: set MTU")
	if dcfg != nil {
		nameservers := dcfg.Nameservers
		if b.avoidEmptyDNS && len(nameservers) == 0 {
			nameservers = googleDNSServers
		}
		for _, dns := range nameservers {
			if err := builder.AddDNSServer(dns.String()); err != nil {
				return err
			}
		}
		for _, dom := range dcfg.SearchDomains {
			if err := builder.AddSearchDomain(dom.WithoutTrailingDot()); err != nil {
				return err
			}
		}
		b.logger.Logf("updateTUN: set nameservers")
	}

	for _, route := range rcfg.Routes {
		// Normalize route address; Builder.addRoute does not accept non-zero masked bits.
		route = route.Masked()
		if err := builder.AddRoute(route.Addr().String(), int32(route.Bits())); err != nil {
			return err
		}
	}

	for _, route := range rcfg.LocalRoutes {
		addr := route.Addr()
		if addr.IsLoopback() {
			continue // Skip the loopback addresses since VpnService throws an exception for those (both IPv4 and IPv6) - see https://android.googlesource.com/platform/frameworks/base/+/c741553/core/java/android/net/VpnService.java#303
		}
		route = route.Masked()
		if err := builder.ExcludeRoute(route.Addr().String(), int32(route.Bits())); err != nil {
			return err
		}
	}
	b.logger.Logf("updateTUN: added %d routes", len(rcfg.Routes))

	for _, addr := range rcfg.LocalAddrs {
		if err := builder.AddAddress(addr.Addr().String(), int32(addr.Bits())); err != nil {
			return err
		}
	}
	b.logger.Logf("updateTUN: added %d local addrs", len(rcfg.LocalAddrs))

	parcelFD, err := builder.Establish()
	if err != nil {
		if strings.Contains(err.Error(), "INTERACT_ACROSS_USERS") {
			// Update VPN status if VPN interface cannot be created
			b.logger.Logf("updateTUN: could not establish VPN because %v", err)
			vpnService.service.UpdateVpnStatus(false)
			return errMultipleUsers
		}
		return fmt.Errorf("VpnService.Builder.establish: %v", err)
	}
	log.Printf("Setting vpn activity status to true")
	vpnService.service.UpdateVpnStatus(true)
	b.logger.Logf("updateTUN: established VPN")

	if parcelFD == nil {
		return errVPNNotPrepared
	}

	// detachFd.
	tunFD, err := parcelFD.Detach()
	vpnService.fdDetached = true
	vpnService.fd = tunFD

	if err != nil {
		return fmt.Errorf("detachFd: %v", err)
	}
	b.logger.Logf("updateTUN: detached FD")

	// Create TUN device.
	tunDev, _, err := tun.CreateUnmonitoredTUNFromFD(int(tunFD))
	if err != nil {
		closeFileDescriptor()
		return err
	}
	b.logger.Logf("updateTUN: created TUN device")

	b.devices.add(tunDev)
	b.logger.Logf("updateTUN: added TUN device")

	b.lastCfg = rcfg
	b.lastDNSCfg = dcfg
	return nil
}

func closeFileDescriptor() error {
	if vpnService.fd != -1 && vpnService.fdDetached {
		err := syscall.Close(int(vpnService.fd))
		vpnService.fd = -1
		vpnService.fdDetached = false
		return fmt.Errorf("error closing file descriptor: %w", err)
	}
	return nil
}

// CloseVPN closes any active TUN devices.
func (b *backend) CloseTUNs() {
	b.lastCfg = nil
	b.devices.Shutdown()
}

// ifname is the interface name retrieved from LinkProperties on network change. If a network is lost, an empty string is passed in.
func (b *backend) NetworkChanged(ifname string) {
	defer func() {
		if p := recover(); p != nil {
			log.Printf("panic in NetworkChanged %s: %s", p, debug.Stack())
			panic(p)
		}
	}()

	// Set the interface name and alert the monitor.
	netmon.UpdateLastKnownDefaultRouteInterface(ifname)
	if b.sys != nil {
		if nm, ok := b.sys.NetMon.GetOK(); ok {
			nm.InjectEvent()
		}
	}
}

func (b *backend) getDNSBaseConfig() (ret dns.OSConfig, _ error) {
	defer func() {
		// If we couldn't find any base nameservers, ultimately fall back to
		// Google's. Normally Tailscale doesn't ever pick a default nameserver
		// for users but in this case Android's APIs for reading the underlying
		// DNS config are lacking, and almost all Android phones use Google
		// services anyway, so it's a reasonable default: it's an ecosystem the
		// user has selected by having an Android device.
		if len(ret.Nameservers) == 0 && b.appCtx.ShouldUseGoogleDNSFallback() {
			log.Printf("getDNSBaseConfig: none found; falling back to Google public DNS")
			ret.Nameservers = append(ret.Nameservers, googleDNSServers...)
		}
	}()
	baseConfig := b.getPlatformDNSConfig()
	lines := strings.Split(baseConfig, "\n")
	if len(lines) == 0 {
		return dns.OSConfig{}, nil
	}

	config := dns.OSConfig{}
	addrs := strings.Trim(lines[0], " \n")
	for _, addr := range strings.Split(addrs, " ") {
		ip, err := netip.ParseAddr(addr)
		if err == nil {
			config.Nameservers = append(config.Nameservers, ip)
		}
	}

	if len(lines) > 1 {
		for _, s := range strings.Split(strings.Trim(lines[1], " \n"), " ") {
			domain, err := dnsname.ToFQDN(s)
			if err != nil {
				log.Printf("getDNSBaseConfig: unable to parse %q: %v", s, err)
				continue
			}
			config.SearchDomains = append(config.SearchDomains, domain)
		}
	}

	return config, nil
}

func (b *backend) getPlatformDNSConfig() string {
	return b.appCtx.GetPlatformDNSConfig()
}

func (b *backend) setCfg(rcfg *router.Config, dcfg *dns.OSConfig) error {
	return b.settings(rcfg, dcfg)
}
