package network

import (
	"bufio"
	"fmt"
	"os"
	"strings"
	"sync"
)

const ConfigFile = "/etc/network/interfaces"

// InterfaceConfig represents one stanza in /etc/network/interfaces.
type InterfaceConfig struct {
	Name    string            `json:"name"`
	Label   string            `json:"label,omitempty"` // display name, stored in config.yaml only
	Auto   bool              `json:"auto"`
	Mode    string            `json:"mode"`              // dhcp, static, loopback, manual
	Address string            `json:"address,omitempty"` // static only
	Netmask string            `json:"netmask,omitempty"`
	Gateway string            `json:"gateway,omitempty"`
	DNS     []string          `json:"dns,omitempty"`
	Extra   map[string]string `json:"extra,omitempty"` // other raw options
}

// --- Pending config store (in-memory, not yet written to disk) ---

var (
	pendingMu sync.Mutex
	pending   = map[string]*InterfaceConfig{}
)

func GetPendingConfig(name string) *InterfaceConfig {
	pendingMu.Lock()
	defer pendingMu.Unlock()
	return pending[name]
}

func SetPendingConfig(cfg *InterfaceConfig) {
	pendingMu.Lock()
	defer pendingMu.Unlock()
	pending[cfg.Name] = cfg
}

func ClearPendingConfig(name string) {
	pendingMu.Lock()
	defer pendingMu.Unlock()
	delete(pending, name)
}

func ClearAllPending() {
	pendingMu.Lock()
	defer pendingMu.Unlock()
	pending = map[string]*InterfaceConfig{}
}

func GetAllPending() map[string]*InterfaceConfig {
	pendingMu.Lock()
	defer pendingMu.Unlock()
	out := make(map[string]*InterfaceConfig, len(pending))
	for k, v := range pending {
		cp := *v
		out[k] = &cp
	}
	return out
}

// --- Parse /etc/network/interfaces ---

func ParseConfig() (map[string]*InterfaceConfig, error) {
	f, err := os.Open(ConfigFile)
	if err != nil {
		if os.IsNotExist(err) {
			return map[string]*InterfaceConfig{}, nil
		}
		return nil, fmt.Errorf("open %s: %w", ConfigFile, err)
	}
	defer f.Close()

	configs := map[string]*InterfaceConfig{}
	autoSet := map[string]bool{}
	var cur *InterfaceConfig

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := scanner.Text()
		// Strip inline comments
		if idx := strings.Index(line, "#"); idx >= 0 {
			line = line[:idx]
		}
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}

		fields := strings.Fields(line)
		switch fields[0] {
		case "auto":
			for _, n := range fields[1:] {
				autoSet[n] = true
			}

		case "iface":
			// iface <name> <family> <method>
			if len(fields) < 4 {
				continue
			}
			cur = &InterfaceConfig{
				Name:  fields[1],
				Mode:  fields[3],
				Extra: map[string]string{},
			}
			configs[fields[1]] = cur

		default:
			if cur == nil || len(fields) < 2 {
				continue
			}
			val := strings.Join(fields[1:], " ")
			switch fields[0] {
			case "address":
				cur.Address = val
			case "netmask":
				cur.Netmask = val
			case "gateway":
				cur.Gateway = val
			case "dns-nameservers":
				cur.DNS = fields[1:]
			default:
				cur.Extra[fields[0]] = val
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}

	for name, cfg := range configs {
		cfg.Auto = autoSet[name]
	}
	return configs, nil
}

// --- Write /etc/network/interfaces ---

func WriteConfig(configs map[string]*InterfaceConfig) error {
	// Backup
	if data, err := os.ReadFile(ConfigFile); err == nil {
		_ = os.WriteFile(ConfigFile+".bak", data, 0644)
	}

	f, err := os.Create(ConfigFile)
	if err != nil {
		return fmt.Errorf("create %s: %w", ConfigFile, err)
	}
	defer f.Close()

	// loopback first, then physical interfaces, then VLANs (sorted within each group)
	if lo, ok := configs["lo"]; ok {
		writeStanza(f, lo)
	}
	for name, cfg := range configs {
		if name == "lo" || IsVLAN(name) {
			continue
		}
		writeStanza(f, cfg)
	}
	for name, cfg := range configs {
		if !IsVLAN(name) {
			continue
		}
		writeStanza(f, cfg)
	}
	return nil
}

func writeStanza(f *os.File, c *InterfaceConfig) {
	if c.Auto {
		fmt.Fprintf(f, "auto %s\n", c.Name)
	}
	family := "inet"
	fmt.Fprintf(f, "iface %s %s %s\n", c.Name, family, c.Mode)

	if c.Mode == "static" {
		if c.Address != "" {
			fmt.Fprintf(f, "\taddress %s\n", c.Address)
		}
		if c.Netmask != "" {
			fmt.Fprintf(f, "\tnetmask %s\n", c.Netmask)
		}
		if c.Gateway != "" {
			fmt.Fprintf(f, "\tgateway %s\n", c.Gateway)
		}
		if len(c.DNS) > 0 {
			fmt.Fprintf(f, "\tdns-nameservers %s\n", strings.Join(c.DNS, " "))
		}
	}
	// VLAN interfaces need vlan-raw-device unless already in Extra
	if IsVLAN(c.Name) {
		if _, ok := c.Extra["vlan-raw-device"]; !ok {
			fmt.Fprintf(f, "\tvlan-raw-device %s\n", VLANParent(c.Name))
		}
	}
	for k, v := range c.Extra {
		fmt.Fprintf(f, "\t%s %s\n", k, v)
	}
	fmt.Fprintln(f)
}
