package config

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
)

// AutoConnectRule defines a rule for automatic device connection
type AutoConnectRule struct {
	BusID      string `json:"bus_id,omitempty"`
	VendorID   string `json:"vendor_id,omitempty"`
	ProductID  string `json:"product_id,omitempty"`
	ClientName string `json:"client_name,omitempty"`
}

// Config holds the client configuration
type Config struct {
	RelayAddr string `json:"relay_addr"` // e.g. "ws://localhost:8443" or "wss://relay.example.com:8443"
	Hash      string `json:"hash"`       // SHA256 hash of 3 tokens
	Mode      string `json:"mode"`       // "share", "use" or "both"
	Name      string `json:"name"`       // friendly name for this client
	WebPort   int    `json:"web_port"`   // web UI port (default 8080)

	// Tokens (optional, stored for convenience - hash is what matters)
	Token1 string `json:"token1,omitempty"`
	Token2 string `json:"token2,omitempty"`
	Token3 string `json:"token3,omitempty"`

	// Auto-connect rules (use mode only)
	AutoConnect []AutoConnectRule `json:"auto_connect,omitempty"`

	// Share mode: allow other clients to force-detach devices in use
	AllowForceDetach bool `json:"allow_force_detach,omitempty"`

	// DirectPort is the TCP port to accept direct tunnel connections on.
	// 0 picks a free port, which is fine when peers can reach each other
	// directly. Set a fixed port when you need to forward it through a
	// firewall or NAT.
	DirectPort int `json:"direct_port,omitempty"`

	// DisableDirect forces every tunnel through the relay. Direct connections
	// are preferred otherwise: they cut latency and keep USB traffic away
	// from the relay entirely.
	DisableDirect bool `json:"disable_direct,omitempty"`

	// BridgeSocket is a Unix socket path on which to accept USB devices
	// handed in by another process. Needed where this process cannot open
	// devices itself — an Android app must obtain the descriptor through the
	// framework and pass it in. Empty disables the bridge.
	BridgeSocket string `json:"bridge_socket,omitempty"`
}

// HasTokens reports whether the full token set is available.
//
// Tunnel encryption and direct connections both need the tokens themselves;
// a config carrying only the group hash can join a group but not derive the
// keys, because the hash is what the relay is told.
func (c *Config) HasTokens() bool {
	return c.Token1 != "" && c.Token2 != "" && c.Token3 != ""
}

// DefaultConfig returns a config with sensible defaults
func DefaultConfig() *Config {
	hostname, _ := os.Hostname()
	if hostname == "" {
		hostname = "unknown"
	}
	return &Config{
		RelayAddr: "ws://localhost:8443",
		Mode:      "use",
		Name:      hostname,
		WebPort:   8080,
	}
}

// DefaultConfigPath returns the default config file path
func DefaultConfigPath() string {
	home, err := os.UserHomeDir()
	if err != nil {
		return "usb-client.json"
	}
	return filepath.Join(home, ".usb-server", "config.json")
}

// Load reads config from a JSON file
func Load(path string) (*Config, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("reading config: %w", err)
	}

	cfg := DefaultConfig()
	if err := json.Unmarshal(data, cfg); err != nil {
		return nil, fmt.Errorf("parsing config: %w", err)
	}

	return cfg, nil
}

// Save writes config to a JSON file
func (c *Config) Save(path string) error {
	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0700); err != nil {
		return fmt.Errorf("creating config directory: %w", err)
	}

	data, err := json.MarshalIndent(c, "", "  ")
	if err != nil {
		return fmt.Errorf("encoding config: %w", err)
	}

	if err := os.WriteFile(path, data, 0600); err != nil {
		return fmt.Errorf("writing config: %w", err)
	}

	return nil
}
