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

package libtailscale

import (
	"log"

	_ "golang.org/x/mobile/bind"
)

// Start starts the application, storing state in the given dataDir and using
// the given appCtx.
func Start(dataDir, directFileRoot string, appCtx AppContext) Application {
	return start(dataDir, directFileRoot, appCtx)
}

// AppContext provides a context within which the Application is running. This
// context is a hook into functionality that's implemented on the Java side.
type AppContext interface {
	// Log logs the given tag and logLine
	Log(tag, logLine string)

	// EncryptToPref stores the given value to an encrypted preference at the
	// given key.
	EncryptToPref(key, value string) error

	// DecryptFromPref retrieves the given value from an encrypted preference
	// at the given key, or returns empty string if unset.
	DecryptFromPref(key string) (string, error)

	// GetOSVersion gets the Android version.
	GetOSVersion() (string, error)

	// GetModelName gets the Android device's model name.
	GetModelName() (string, error)

	// GetInstallSource gets information about how the app was installed or updated.
	GetInstallSource() string

	// ShouldUseGoogleDNSFallback reports whether or not to use Google for DNS fallback.
	ShouldUseGoogleDNSFallback() bool

	// IsChromeOS reports whether we're on a ChromeOS device.
	IsChromeOS() (bool, error)

	// GetInterfacesAsString gets a string representation of all network
	// interfaces.
	GetInterfacesAsString() (string, error)

	// GetPlatformDNSConfig gets a string representation of the current DNS
	// configuration.
	GetPlatformDNSConfig() string

	// GetSyspolicyStringValue returns the current string value for the given system policy.
	GetSyspolicyStringValue(key string) (string, error)

	// GetSyspolicyBooleanValue returns whether the given system policy is enabled.
	GetSyspolicyBooleanValue(key string) (bool, error)

	// GetSyspolicyStringArrayValue returns the current string array value for the given system policy,
	// expressed as a JSON string.
	GetSyspolicyStringArrayJSONValue(key string) (string, error)
}

// IPNService corresponds to our IPNService in Java.
type IPNService interface {
	// ID returns the unique ID of this instance of the IPNService. Every time
	// we start a new IPN service, it should have a new ID.
	ID() string

	// Protect protects socket identified by the given file descriptor from
	// being captured by the VPN. The return value indicates whether or not the
	// socket was successfully protected.
	Protect(fd int32) bool

	// NewBuilder creates a new VPNServiceBuilder in preparation for starting
	// the Android VPN.
	NewBuilder() VPNServiceBuilder

	Close()

	DisconnectVPN()

	UpdateVpnStatus(bool)
}

// VPNServiceBuilder corresponds to Android's VpnService.Builder.
type VPNServiceBuilder interface {
	SetMTU(int32) error
	AddDNSServer(string) error
	AddSearchDomain(string) error
	AddRoute(string, int32) error
	ExcludeRoute(string, int32) error
	AddAddress(string, int32) error
	Establish() (ParcelFileDescriptor, error)
}

// ParcelFileDescriptor corresponds to Android's ParcelFileDescriptor.
type ParcelFileDescriptor interface {
	Detach() (int32, error)
}

// Application encapsulates the running Tailscale Application. There is only a
// single instance of Application per Android application.
type Application interface {
	// CallLocalAPI provides a mechanism for calling Tailscale's HTTP localapi
	// without having to call over the network.
	CallLocalAPI(timeoutMillis int, method, endpoint string, body InputStream) (LocalAPIResponse, error)

	// CallLocalAPIMultipart is like CallLocalAPI, but instead of a single body,
	// it accepts multiple FileParts that get encoded as multipart/form-data.
	CallLocalAPIMultipart(timeoutMillis int, method, endpoint string, parts FileParts) (LocalAPIResponse, error)

	// NotifyPolicyChanged notifies the backend about a changed MDM policy,
	// so it can re-read it via the [syspolicyHandler].
	NotifyPolicyChanged()

	// WatchNotifications provides a mechanism for subscribing to ipn.Notify
	// updates. The given NotificationCallback's OnNotify function is invoked
	// on every new ipn.Notify message. The returned NotificationManager
	// allows the watcher to stop watching notifications.
	WatchNotifications(mask int, cb NotificationCallback) NotificationManager
}

// FileParts is an array of multiple FileParts.
type FileParts interface {
	Len() int32
	Get(int32) *FilePart
}

// FilePart is a multipart file that can be submitted via CallLocalAPIMultiPart.
type FilePart struct {
	ContentLength int64
	Filename      string
	Body          InputStream
	ContentType   string // optional MIME content type
}

// LocalAPIResponse is a response to a localapi call, analogous to an http.Response.
type LocalAPIResponse interface {
	StatusCode() int
	BodyBytes() ([]byte, error)
	BodyInputStream() InputStream
}

// NotificationCallback is callback for receiving ipn.Notify messages.
type NotificationCallback interface {
	OnNotify([]byte) error
}

// NotificationManager provides a mechanism for a notification watcher to stop
// watching notifications.
type NotificationManager interface {
	Stop()
}

// InputStream provides an adapter between Java's InputStream and Go's
// io.Reader.
type InputStream interface {
	Read() ([]byte, error)
	Close() error
}

// The below are global callbacks that allow the Java application to notify Go
// of various state changes.

func RequestVPN(service IPNService) {
	onVPNRequested <- service
}

func ServiceDisconnect(service IPNService) {
	onDisconnect <- service
}

func SendLog(logstr []byte) {
	select {
	case onLog <- string(logstr):
		// Successfully sent log
	default:
		// Channel is full, log not sent
		log.Printf("Log %v not sent", logstr) // missing argument in original code
	}
}
