package awg

import (
	"context"
	"fmt"
	"io"
	"os"
	"sync"
	"time"

	"github.com/amnezia-vpn/amneziawg-go/conn"
	"github.com/amnezia-vpn/amneziawg-go/device"
	"github.com/amnezia-vpn/amneziawg-go/tun/netstack"

	"vpnclient/internal/config"
)

// Engine runs AmneziaWG 2.0 in userspace (gvisor netstack) and exposes local SOCKS/HTTP.
type Engine struct {
	mu      sync.Mutex
	dev     *device.Device
	tnet    *netstack.Net
	proxies *proxyServer
	profile config.Profile
	stderr  io.Writer
	running bool
}

func New(stderr io.Writer) *Engine {
	if stderr == nil {
		stderr = os.Stderr
	}
	return &Engine{stderr: stderr}
}

func (e *Engine) Protocol() config.Protocol { return config.ProtocolAWG }

func (e *Engine) Start(ctx context.Context, profile config.Profile, _ string) error {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.running {
		return fmt.Errorf("awg: already running")
	}

	cfg, err := Parse(profile.Proxy)
	if err != nil {
		return err
	}
	ipc, err := cfg.ToIPC()
	if err != nil {
		return err
	}
	addrs, err := cfg.LocalAddrs()
	if err != nil {
		return err
	}

	tunDev, tnet, err := netstack.CreateNetTUN(addrs, cfg.DNSAddrs(), cfg.EffectiveMTU())
	if err != nil {
		return fmt.Errorf("awg netstack: %w", err)
	}
	logger := &device.Logger{
		Verbosef: func(format string, args ...any) {},
		Errorf: func(format string, args ...any) {
			fmt.Fprintf(e.stderr, "awg: "+format+"\n", args...)
		},
	}
	dev := device.NewDevice(tunDev, conn.NewDefaultBind(), logger)
	if err := dev.IpcSet(ipc); err != nil {
		dev.Close()
		return fmt.Errorf("awg configure: %w", err)
	}
	if err := dev.Up(); err != nil {
		dev.Close()
		return fmt.Errorf("awg up: %w", err)
	}

	socksHP, _ := profile.SOCKSListenHostPort()
	httpHP, _ := profile.HTTPListenHostPort()
	if socksHP == "" {
		socksHP = "127.0.0.1:1080"
	}
	if httpHP == "" {
		httpHP = "127.0.0.1:1081"
	}

	proxies, err := startProxies(socksHP, httpHP, tnet.DialContext)
	if err != nil {
		dev.Close()
		return err
	}

	e.dev = dev
	e.tnet = tnet
	e.proxies = proxies
	e.profile = profile
	e.running = true

	// Brief handshake grace; don't fail hard if peer is slow.
	select {
	case <-ctx.Done():
		_ = e.stopLocked()
		return ctx.Err()
	case <-time.After(400 * time.Millisecond):
	}
	return nil
}

func (e *Engine) Stop() error {
	e.mu.Lock()
	defer e.mu.Unlock()
	return e.stopLocked()
}

func (e *Engine) stopLocked() error {
	if !e.running {
		return nil
	}
	if e.proxies != nil {
		_ = e.proxies.Close()
		e.proxies = nil
	}
	if e.dev != nil {
		e.dev.Close()
		e.dev = nil
	}
	e.tnet = nil
	e.running = false
	return nil
}

func (e *Engine) Running() bool {
	e.mu.Lock()
	defer e.mu.Unlock()
	return e.running
}

func (e *Engine) LocalHTTPProxy() (string, bool) {
	e.mu.Lock()
	defer e.mu.Unlock()
	if !e.running {
		return "", false
	}
	if e.proxies != nil && e.proxies.httpAddr != "" {
		return e.proxies.httpAddr, true
	}
	return e.profile.HTTPListenHostPort()
}

func (e *Engine) LocalSOCKSProxy() (string, bool) {
	e.mu.Lock()
	defer e.mu.Unlock()
	if !e.running {
		return "", false
	}
	if e.proxies != nil && e.proxies.socksAddr != "" {
		return e.proxies.socksAddr, true
	}
	return e.profile.SOCKSListenHostPort()
}

// EnsureBinary is a no-op: AWG is embedded via amneziawg-go.
func EnsureBinary(_ string) (string, error) {
	return "embedded-amneziawg-go", nil
}

// ResolveBinary reports the embedded core.
func ResolveBinary(_ string) (string, error) {
	return "embedded-amneziawg-go", nil
}
