package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
	"time"

	"vpnclient/internal/config"
	"vpnclient/internal/core"
	"vpnclient/internal/protocols/hysteria2"
	"vpnclient/internal/protocols/naive"
)

func main() {
	if len(os.Args) < 2 {
		printUsage()
		os.Exit(2)
	}

	cmd := os.Args[1]
	args := os.Args[2:]

	switch cmd {
	case "connect":
		os.Exit(runConnect(args))
	case "disconnect":
		fmt.Fprintln(os.Stderr, "disconnect: use Ctrl+C on a running connect session")
		os.Exit(2)
	case "status":
		fmt.Fprintln(os.Stderr, "status: only available while connect is running in this process")
		os.Exit(2)
	case "install-core":
		os.Exit(runInstallCore(args))
	case "init":
		os.Exit(runInit(args))
	case "probe":
		os.Exit(runProbe(args))
	case "set-proxy":
		os.Exit(runSetProxy(args))
	case "help", "-h", "--help":
		printUsage()
	default:
		fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
		printUsage()
		os.Exit(2)
	}
}

func printUsage() {
	fmt.Fprintf(os.Stderr, `VPN client (NaiveProxy) for Windows

Usage:
  vpnclient init [-config configs/config.json]
  vpnclient install-core [-config configs/config.json]
  vpnclient set-proxy [-config configs/config.json] <share-or-proxy-uri>
  vpnclient connect [-config configs/config.json] [-profile name] [-no-sysproxy] [-probe]
  vpnclient probe [-config configs/config.json] [-profile name] [-url URL]

Share links like naive+https://user:pass@host:443#name are accepted.

Architecture:
  This client runs the official naive.exe from klzgrad/naiveproxy (Chromium
  network stack) and optionally sets the Windows system HTTP proxy so apps
  use the tunnel. That is a real NaiveProxy session, not a simulation.

`)
}

func defaultConfigPath() string {
	return filepath.Join("configs", "config.json")
}

func runInit(args []string) int {
	fs := flag.NewFlagSet("init", flag.ExitOnError)
	cfgPath := fs.String("config", defaultConfigPath(), "path to write example config")
	_ = fs.Parse(args)
	if _, err := os.Stat(*cfgPath); err == nil {
		fmt.Fprintf(os.Stderr, "refusing to overwrite existing %s\n", *cfgPath)
		return 1
	}
	if err := config.WriteExample(*cfgPath); err != nil {
		fmt.Fprintf(os.Stderr, "init: %v\n", err)
		return 1
	}
	fmt.Printf("wrote %s — edit proxy user/pass/host, then: vpnclient install-core && vpnclient connect\n", *cfgPath)
	return 0
}

func runInstallCore(args []string) int {
	fs := flag.NewFlagSet("install-core", flag.ExitOnError)
	cfgPath := fs.String("config", defaultConfigPath(), "config path (for bin_dir)")
	binDirFlag := fs.String("bin-dir", "", "override bin directory")
	_ = fs.Parse(args)

	binDir := *binDirFlag
	if binDir == "" {
		if cfg, err := config.Load(*cfgPath); err == nil {
			resolved, err := config.ResolveBinDir(*cfgPath, cfg.BinDir)
			if err != nil {
				fmt.Fprintf(os.Stderr, "install-core: %v\n", err)
				return 1
			}
			binDir = resolved
		} else {
			binDir = "bin"
		}
	}
	pathNaive, err := naive.EnsureBinary(binDir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "install-core naive: %v\n", err)
		return 1
	}
	fmt.Printf("naive core ready: %s\n", pathNaive)
	pathHy2, err := hysteria2.EnsureBinary(binDir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "install-core hysteria2: %v\n", err)
		return 1
	}
	fmt.Printf("hysteria2 core ready: %s\n", pathHy2)
	return 0
}

func loadManager(cfgPath string) (*core.Manager, *config.Config, error) {
	cfg, err := config.Load(cfgPath)
	if err != nil {
		return nil, nil, err
	}
	mgr, err := core.NewManager(cfgPath, cfg, os.Stderr)
	if err != nil {
		return nil, nil, err
	}
	return mgr, cfg, nil
}

func runConnect(args []string) int {
	fs := flag.NewFlagSet("connect", flag.ExitOnError)
	cfgPath := fs.String("config", defaultConfigPath(), "config path")
	profile := fs.String("profile", "", "profile name (default: config.active)")
	noSys := fs.Bool("no-sysproxy", false, "do not change Windows system proxy")
	doProbe := fs.Bool("probe", false, "verify tunnel with an HTTP request after connect")
	_ = fs.Parse(args)

	mgr, cfg, err := loadManager(*cfgPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "connect: %v\n", err)
		return 1
	}
	if *noSys {
		cfg.SystemProxy = false
	}

	// Ensure official core is present.
	if _, err := mgr.EnsureCore(""); err != nil {
		fmt.Fprintf(os.Stderr, "connect: %v\n", err)
		return 1
	}

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	if err := mgr.Connect(ctx, *profile); err != nil {
		fmt.Fprintf(os.Stderr, "connect: %v\n", err)
		return 1
	}
	defer func() {
		if err := mgr.Disconnect(); err != nil {
			fmt.Fprintf(os.Stderr, "disconnect: %v\n", err)
		}
	}()

	st := mgr.Status()
	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	_ = enc.Encode(st)
	fmt.Fprintln(os.Stderr, "connected — press Ctrl+C to disconnect and restore system proxy")

	if *doProbe {
		pctx, cancel := context.WithTimeout(ctx, 45*time.Second)
		err := mgr.Probe(pctx, "")
		cancel()
		if err != nil {
			fmt.Fprintf(os.Stderr, "probe failed: %v\n", err)
		} else {
			fmt.Fprintln(os.Stderr, "probe ok")
		}
	}

	<-ctx.Done()
	fmt.Fprintln(os.Stderr, "shutting down...")
	return 0
}

func runProbe(args []string) int {
	fs := flag.NewFlagSet("probe", flag.ExitOnError)
	cfgPath := fs.String("config", defaultConfigPath(), "config path")
	profile := fs.String("profile", "", "profile name")
	testURL := fs.String("url", "https://www.google.com/generate_204", "URL to fetch via tunnel")
	_ = fs.Parse(args)

	mgr, cfg, err := loadManager(*cfgPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "probe: %v\n", err)
		return 1
	}
	cfg.SystemProxy = false

	if _, err := mgr.EnsureCore(""); err != nil {
		fmt.Fprintf(os.Stderr, "probe: %v\n", err)
		return 1
	}

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	if err := mgr.Connect(ctx, *profile); err != nil {
		fmt.Fprintf(os.Stderr, "probe: connect: %v\n", err)
		return 1
	}
	defer mgr.Disconnect()

	pctx, cancel := context.WithTimeout(ctx, 45*time.Second)
	defer cancel()
	if err := mgr.Probe(pctx, *testURL); err != nil {
		fmt.Fprintf(os.Stderr, "probe failed: %v\n", err)
		return 1
	}
	fmt.Println("probe ok")
	return 0
}

func runSetProxy(args []string) int {
	fs := flag.NewFlagSet("set-proxy", flag.ExitOnError)
	cfgPath := fs.String("config", defaultConfigPath(), "config path")
	_ = fs.Parse(args)
	rest := fs.Args()
	if len(rest) < 1 {
		fmt.Fprintln(os.Stderr, "usage: vpnclient set-proxy <uri>")
		return 2
	}
	mgr, _, err := loadManager(*cfgPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "set-proxy: %v\n", err)
		return 1
	}
	if err := mgr.UpdateProxyURI(rest[0]); err != nil {
		fmt.Fprintf(os.Stderr, "set-proxy: %v\n", err)
		return 1
	}
	fmt.Println("proxy saved")
	return 0
}
