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

package tsnet_test

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"path/filepath"

	"tailscale.com/tsnet"
)

// ExampleServer shows you how to construct a ready-to-use tsnet instance.
func ExampleServer() {
	srv := new(tsnet.Server)
	if err := srv.Start(); err != nil {
		log.Fatalf("can't start tsnet server: %v", err)
	}
	defer srv.Close()
}

// ExampleServer_hostname shows you how to set a tsnet server's hostname.
//
// This setting lets you control the host name of your program on your
// tailnet. By default this will be the name of your program (such as foo
// for a program stored at /usr/local/bin/foo). You can also override this
// by setting the Hostname field.
func ExampleServer_hostname() {
	srv := &tsnet.Server{
		Hostname: "kirito",
	}

	// do something with srv
	_ = srv
}

// ExampleServer_dir shows you how to configure the persistent directory for
// a tsnet application. This is where the Tailscale node information is stored
// so that your application can reconnect to your tailnet when the application
// is restarted.
//
// By default, tsnet will store data in your user configuration directory based
// on the name of the binary. Note that this folder must already exist or tsnet
// calls will fail.
func ExampleServer_dir() {
	dir := filepath.Join("/data", "tsnet")

	if err := os.MkdirAll(dir, 0700); err != nil {
		log.Fatal(err)
	}

	srv := &tsnet.Server{
		Dir: dir,
	}

	// do something with srv
	_ = srv
}

// ExampleServer_multipleInstances shows you how to configure multiple instances
// of tsnet per program. This allows you to have multiple Tailscale nodes in the
// same process/container.
func ExampleServer_multipleInstances() {
	baseDir := "/data"
	var servers []*tsnet.Server
	for _, hostname := range []string{"ichika", "nino", "miku", "yotsuba", "itsuki"} {
		os.MkdirAll(filepath.Join(baseDir, hostname), 0700)
		srv := &tsnet.Server{
			Hostname:  hostname,
			AuthKey:   os.Getenv("TS_AUTHKEY"),
			Ephemeral: true,
			Dir:       filepath.Join(baseDir, hostname),
		}
		if err := srv.Start(); err != nil {
			log.Fatalf("can't start tsnet server: %v", err)
		}
		servers = append(servers, srv)
	}

	// When you're done, close the instances
	defer func() {
		for _, srv := range servers {
			srv.Close()
		}
	}()
}

// ExampleServer_ignoreLogsSometimes shows you how to ignore all of the log messages
// written by a tsnet instance, but allows you to opt-into them if a command-line
// flag is set.
func ExampleServer_ignoreLogsSometimes() {
	tsnetVerbose := flag.Bool("tsnet-verbose", false, "if set, verbosely log tsnet information")
	hostname := flag.String("tsnet-hostname", "hikari", "hostname to use on the tailnet")

	srv := &tsnet.Server{
		Hostname: *hostname,
	}

	if *tsnetVerbose {
		srv.Logf = log.New(os.Stderr, fmt.Sprintf("[tsnet:%s] ", *hostname), log.LstdFlags).Printf
	}
}

// ExampleServer_HTTPClient shows you how to make HTTP requests over your tailnet.
//
// If you want to make outgoing HTTP connections to resources on your tailnet, use
// the HTTP client that the tsnet.Server exposes.
func ExampleServer_HTTPClient() {
	srv := &tsnet.Server{}
	cli := srv.HTTPClient()

	resp, err := cli.Get("https://hello.ts.net")
	if resp == nil {
		log.Fatal(err)
	}
	// do something with resp
	_ = resp
}

// ExampleServer_Start demonstrates the Start method, which should be called if
// you need to explicitly start it. Note that the Start method is implicitly
// called if needed.
func ExampleServer_Start() {
	srv := new(tsnet.Server)

	if err := srv.Start(); err != nil {
		log.Fatal(err)
	}

	// Be sure to close the server instance at some point. It will stay open until
	// either the OS process ends or the server is explicitly closed.
	defer srv.Close()
}

// ExampleServer_Listen shows you how to create a TCP listener on your tailnet and
// then makes an HTTP server on top of that.
func ExampleServer_Listen() {
	srv := &tsnet.Server{
		Hostname: "tadaima",
	}

	ln, err := srv.Listen("tcp", ":80")
	if err != nil {
		log.Fatal(err)
	}

	log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hi there! Welcome to the tailnet!")
	})))
}

// ExampleServer_ListenTLS shows you how to create a TCP listener on your tailnet and
// then makes an HTTPS server on top of that.
func ExampleServer_ListenTLS() {
	srv := &tsnet.Server{
		Hostname: "aegis",
	}

	ln, err := srv.ListenTLS("tcp", ":443")
	if err != nil {
		log.Fatal(err)
	}

	log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hi there! Welcome to the tailnet!")
	})))
}

// ExampleServer_ListenFunnel shows you how to create an HTTPS service on both your tailnet
// and the public internet via Funnel.
func ExampleServer_ListenFunnel() {
	srv := &tsnet.Server{
		Hostname: "ophion",
	}

	ln, err := srv.ListenFunnel("tcp", ":443")
	if err != nil {
		log.Fatal(err)
	}

	log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hi there! Welcome to the tailnet!")
	})))
}

// ExampleServer_ListenFunnel_funnelOnly shows you how to create a funnel-only HTTPS service.
func ExampleServer_ListenFunnel_funnelOnly() {
	srv := new(tsnet.Server)
	srv.Hostname = "ophion"
	ln, err := srv.ListenFunnel("tcp", ":443", tsnet.FunnelOnly())
	if err != nil {
		log.Fatal(err)
	}

	log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hi there! Welcome to the tailnet!")
	})))
}

// ExampleServer_ListenService demonstrates how to advertise an HTTPS Service.
func ExampleServer_ListenService() {
	srv := &tsnet.Server{
		Hostname: "atum",
	}

	ln, err := srv.ListenService("svc:my-service", tsnet.ServiceModeHTTP{
		HTTPS: true,
		Port:  443,
	})
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Listening on https://%v\n", ln.FQDN)
	log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "<html><body><h1>Hello, tailnet!</h1>")
	})))
}

// ExampleServer_ListenService_reverseProxy demonstrates how to advertise a
// Service targeting a reverse proxy. This is useful when the backing server is
// external to the tsnet application.
func ExampleServer_ListenService_reverseProxy() {
	// targetAddress represents the address of the backing server.
	const targetAddress = "1.2.3.4:80"

	// We will use a reverse proxy to direct traffic to the backing server.
	reverseProxy := httputil.NewSingleHostReverseProxy(&url.URL{
		Scheme: "http",
		Host:   targetAddress,
	})

	srv := &tsnet.Server{
		Hostname: "tefnut",
	}

	ln, err := srv.ListenService("svc:my-service", tsnet.ServiceModeHTTP{
		HTTPS: true,
		Port:  443,
	})
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Listening on https://%v\n", ln.FQDN)
	log.Fatal(http.Serve(ln, reverseProxy))
}
