// Package client implements the SoftEther VPN client handshake and session management.
//
// The connection flow follows the SoftEther protocol:
//  1. TLS connect (standard TLS, looks like HTTPS)
//  2. Upload signature ("VPNCONNECT" string via HTTP POST)
//  3. Download Hello (server sends version info + random challenge)
//  4. Upload Auth (client sends credentials + node info via HTTP POST)
//  5. Download Welcome (server sends session info if auth succeeds)
//  6. Enter tunneling mode (raw TCP block framing, no more HTTP)
//
// Reference: https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Protocol.c#L7145 (ClientConnectToServer flow)
package client

import (
	"crypto/rand"
	"fmt"
	"os"
	"runtime"

	"git.sagar.ch/sagar/softether-go/pkg/protocol"
)

const (
	clientStr   = "Softether Go Client"
	clientVer   = 502
	clientBuild = 5187

	// Auth types matching SoftEther's CLIENT_AUTHTYPE_* constants.
	// See: https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Cedar.h#L554
	authTypePassword      uint32 = 1
	authTypePlainPassword uint32 = 2
)

// Config holds the connection parameters for a SoftEther VPN session.
type Config struct {
	Host               string // Server hostname or IP
	Port               int    // Server port (default: 443)
	Hub                string // Virtual hub name
	Username           string // Authentication username
	Password           string // Password (hashed or sent plain depending on PlainPassword)
	PlainPassword      bool   // If true, send password as plaintext (AuthType 2)
	InsecureSkipVerify bool   // Skip TLS certificate verification
}

// Session represents an established SoftEther VPN session.
// After Connect() returns, the underlying Conn is ready for TCP block framing.
type Session struct {
	Conn           *protocol.Conn
	SessionKey     [sha1Size]byte // 20-byte session key from server
	SessionKey32   uint32         // 32-bit session key
	SessionName    string         // Server-assigned session name (e.g. "SID-ADMIN-1")
	ConnectionName string         // Server-assigned connection name
}

// Connect performs the full SoftEther handshake and returns an established session.
// The returned Session's Conn is ready for TCP block framing (tunnel mode).
func Connect(cfg Config) (*Session, error) {
	if cfg.Port == 0 {
		cfg.Port = 443
	}

	// Step 1: TLS connection
	conn, err := protocol.DialTLS(cfg.Host, cfg.Port, cfg.InsecureSkipVerify)
	if err != nil {
		return nil, fmt.Errorf("connect: %w", err)
	}

	// Step 2: Upload signature
	// See: https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Protocol.c#L7208
	if err := protocol.UploadSignature(conn); err != nil {
		conn.Close()
		return nil, fmt.Errorf("signature: %w", err)
	}

	// Step 3: Download Hello — server sends version info and a random challenge
	// See: https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Protocol.c#L7250 (ClientDownloadHello)
	helloPack, err := protocol.RecvPack(conn)
	if err != nil {
		conn.Close()
		return nil, fmt.Errorf("hello recv: %w", err)
	}
	if e := helloPack.GetError(); e != 0 {
		conn.Close()
		return nil, fmt.Errorf("hello error: %d", e)
	}

	serverRandom := helloPack.GetData("random")
	if len(serverRandom) != sha1Size {
		conn.Close()
		return nil, fmt.Errorf("invalid server random size: %d", len(serverRandom))
	}
	var random [sha1Size]byte
	copy(random[:], serverRandom)

	// Step 4: Upload Auth — send credentials and client metadata
	// See: https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Protocol.c#L7289 (ClientUploadAuth)
	authPack := buildAuthPack(cfg, random)

	if err := protocol.SendPack(conn, authPack); err != nil {
		conn.Close()
		return nil, fmt.Errorf("auth send: %w", err)
	}

	// Step 5: Download Welcome — server sends session info if auth succeeded
	welcomePack, err := protocol.RecvPack(conn)
	if err != nil {
		conn.Close()
		return nil, fmt.Errorf("welcome recv: %w", err)
	}

	if e := welcomePack.GetError(); e != 0 {
		conn.Close()
		return nil, fmt.Errorf("auth failed, error code: %d", e)
	}

	sess := &Session{
		Conn:           conn,
		SessionName:    welcomePack.GetStr("session_name"),
		ConnectionName: welcomePack.GetStr("connection_name"),
	}

	sessionKeyData := welcomePack.GetData("session_key")
	if len(sessionKeyData) == sha1Size {
		copy(sess.SessionKey[:], sessionKeyData)
	}
	sess.SessionKey32 = welcomePack.GetInt("session_key_32")

	return sess, nil
}

// swapLE32 byte-swaps a uint32 to encode it as little-endian within a big-endian Pack int.
// SoftEther's OutRpcNodeInfo wraps node info fields with LittleEndian32() before PackAddInt,
// and InRpcNodeInfo reads them with LittleEndian32(PackGetInt(...)). Our Pack writes big-endian,
// so we pre-swap to match the C client's wire format.
func swapLE32(v uint32) uint32 {
	return (v>>24)&0xFF | (v>>8)&0xFF00 | (v<<8)&0xFF0000 | (v<<24)&0xFF000000
}

// buildAuthPack constructs the authentication Pack sent to the server.
// This includes credentials, client version, connection options, node info, and OS info.
// See: https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Protocol.c#L7289
func buildAuthPack(cfg Config, random [sha1Size]byte) *protocol.Pack {
	p := &protocol.Pack{}

	// Login method and credentials
	p.AddStr("method", "login")
	p.AddStr("hubname", cfg.Hub)
	p.AddStr("username", cfg.Username)

	if cfg.PlainPassword {
		// Plain password auth (AuthType 2): password sent as-is over TLS
		// See: https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Protocol.c#L5548
		p.AddInt("authtype", authTypePlainPassword)
		p.AddStr("plain_password", cfg.Password)
	} else {
		// Hashed password auth (AuthType 1): SecurePassword = SHA0(SHA0(password) + server_random)
		hashedPw := HashPassword(cfg.Password)
		securePw := SecurePassword(hashedPw, random)
		p.AddInt("authtype", authTypePassword)
		p.AddData("secure_password", securePw[:])
	}

	// Client version info
	p.AddStr("client_str", clientStr)
	p.AddInt("client_ver", clientVer)
	p.AddInt("client_build", clientBuild)

	// Protocol: 0 = TCP (CONNECTION_TCP), 1 = UDP
	// See: https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Cedar.h#L522
	p.AddInt("protocol", 0)

	// Hello exchange fields
	p.AddStr("hello", clientStr)
	p.AddInt("version", clientVer)
	p.AddInt("build", clientBuild)
	p.AddInt("client_id", 0)

	// Connection options
	p.AddInt("max_connection", 1)
	p.AddBool("use_encrypt", true)
	p.AddBool("use_compress", false)
	p.AddBool("half_connection", false)
	p.AddBool("require_bridge_routing_mode", false)
	p.AddBool("require_monitor_mode", false)
	p.AddBool("qos", true)
	p.AddBool("support_bulk_on_rudp", true)
	p.AddBool("support_hmac_on_bulk_of_rudp", true)
	p.AddBool("support_udp_recovery", true)
	p.AddInt("rudp_bulk_max_version", 2)

	// Machine unique ID (random for this session)
	var uniqueID [sha1Size]byte
	randBytes := make([]byte, 64)
	rand.Read(randBytes)
	uniqueID = sha0(randBytes)
	p.AddData("unique_id", uniqueID[:])

	// Brand string for connection limit
	p.AddStr("branded_ctos", "Branded_VPN")

	// Node info — describes the client machine to the server.
	// See: https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Protocol.c#L8034 (OutRpcNodeInfo)
	hostname, _ := os.Hostname()
	p.AddStr("ClientProductName", clientStr)
	p.AddStr("ServerProductName", "")
	p.AddStr("ClientOsName", runtime.GOOS)
	p.AddStr("ClientOsVer", runtime.GOARCH)
	p.AddStr("ClientOsProductId", "")
	p.AddStr("ClientHostname", hostname)
	p.AddStr("ServerHostname", "")
	p.AddStr("ProxyHostname", "")
	// Note: HubName is NOT added here because "hubname" (lowercase) is already
	// present from the auth fields. SoftEther's AddElement rejects duplicate names
	// (case-insensitive), and PackRead returns NULL on AddElement failure.
	// p.AddStr("HubName", cfg.Hub)
	p.AddData("UniqueId", uniqueID[:16])
	// Node info ints use LittleEndian32 encoding (see Admin.c:OutRpcNodeInfo).
	// The server reads them back with LittleEndian32(PackGetInt(...)).
	p.AddInt("ClientProductVer", swapLE32(clientVer))
	p.AddInt("ClientProductBuild", swapLE32(clientBuild))
	p.AddInt("ServerProductVer", 0)
	p.AddInt("ServerProductBuild", 0)
	p.AddIP4("ClientIpAddress", 0)
	p.AddData("ClientIpAddress6", make([]byte, 16))
	p.AddInt("ClientPort", 0)
	p.AddIP4("ServerIpAddress", 0)
	p.AddData("ServerIpAddress6", make([]byte, 16))
	p.AddInt("ServerPort2", 0)
	p.AddIP4("ProxyIpAddress", 0)
	p.AddData("ProxyIpAddress6", make([]byte, 16))
	p.AddInt("ProxyPort", 0)

	// OS version info (non-Windows)
	// See: https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Protocol.c#L8079 (OutRpcWinVer)
	p.AddBool("V_IsWindows", false)
	p.AddBool("V_IsNT", false)
	p.AddBool("V_IsServer", false)
	p.AddBool("V_IsBeta", false)
	p.AddInt("V_VerMajor", 0)
	p.AddInt("V_VerMinor", 0)
	p.AddInt("V_Build", 0)
	p.AddInt("V_ServicePack", 0)
	p.AddStr("V_Title", "Linux")

	return p
}
