package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"os"
	"io"
	"time"

	"tailscale.com/tsnet"
)

var globalTSServer *tsnet.Server

func startTailscale(dataDir, nodeID, headscaleURL, authKey string) (string, error) {
	// Configure tsnet to use our Headscale server
	os.Setenv("TS_AUTHKEY", authKey)
	os.Setenv("TS_CONTROL_URL", headscaleURL)

	s := &tsnet.Server{
		Hostname: nodeID,
		Dir:      dataDir,
		Logf:     log.Printf,
	}

	if err := s.Start(); err != nil {
		return "", fmt.Errorf("tailscale start: %w", err)
	}

	globalTSServer = s

	// Wait for Tailscale to come up and retrieve IP
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	lc, err := s.LocalClient()
	if err != nil {
		return "", fmt.Errorf("tailscale local client: %w", err)
	}

	var tailscaleIP string
	for {
		status, err := lc.Status(ctx)
		if err != nil {
			return "", fmt.Errorf("tailscale status: %w", err)
		}
		if status.Self != nil && len(status.Self.TailscaleIPs) > 0 {
			tailscaleIP = status.Self.TailscaleIPs[0].String()
			break
		}
		select {
		case <-ctx.Done():
			return "", fmt.Errorf("tailscale IP timeout")
		case <-time.After(1 * time.Second):
		}
	}

	log.Printf("Tailscale started with IP: %s", tailscaleIP)
	return tailscaleIP, nil
}

func startTailscaleProxy(port int) (net.Listener, error) {
	if globalTSServer == nil {
		return nil, fmt.Errorf("tailscale server not started")
	}
	ln, err := globalTSServer.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		return nil, fmt.Errorf("tailscale listen on port %d: %w", port, err)
	}
	go func() {
		for {
			conn, err := ln.Accept()
			if err != nil {
				log.Printf("tailscale proxy accept error on port %d: %v", port, err)
				return
			}
			go handleProxyConn(conn, port)
		}
	}()
	log.Printf("Tailscale proxy started on port %d", port)
	return ln, nil
}

func handleProxyConn(src net.Conn, port int) {
	defer src.Close()
	dst, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
	if err != nil {
		log.Printf("tailscale proxy dial localhost:%d error: %v", port, err)
		return
	}
	defer dst.Close()

	done := make(chan struct{}, 2)
	go func() {
		_, _ = io.Copy(dst, src)
		done <- struct{}{}
	}()
	go func() {
		_, _ = io.Copy(src, dst)
		done <- struct{}{}
	}()
	<-done
}
