package network

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"
)

type InterfaceStats struct {
	Name      string   `json:"name"`
	State     string   `json:"state"`     // up, down, unknown
	IPv4      string   `json:"ipv4"`
	IPv4Mask  string   `json:"ipv4_mask"`
	IPv6      []string `json:"ipv6"`
	Gateway   string   `json:"gateway"`
	RxBytes   uint64   `json:"rx_bytes"`
	TxBytes   uint64   `json:"tx_bytes"`
	RxPackets uint64   `json:"rx_packets"`
	TxPackets uint64   `json:"tx_packets"`
	Mode      string   `json:"mode"` // dhcp, static, loopback, manual, unknown
}

// GetInterfaces returns all network interface names from /sys/class/net.
func GetInterfaces() ([]string, error) {
	entries, err := os.ReadDir("/sys/class/net")
	if err != nil {
		return nil, fmt.Errorf("read /sys/class/net: %w", err)
	}
	names := make([]string, 0, len(entries))
	for _, e := range entries {
		names = append(names, e.Name())
	}
	return names, nil
}

// GetInterfaceStats returns current runtime state of an interface.
func GetInterfaceStats(name string) (*InterfaceStats, error) {
	s := &InterfaceStats{Name: name, IPv6: []string{}}

	// Operational state
	if raw, err := os.ReadFile("/sys/class/net/" + name + "/operstate"); err == nil {
		s.State = strings.TrimSpace(string(raw))
	} else {
		s.State = "unknown"
	}

	// IP addresses
	if out, err := exec.Command("ip", "addr", "show", "dev", name).Output(); err == nil {
		parseIPAddr(string(out), s)
	}

	// Default gateway for this interface
	if out, err := exec.Command("ip", "route", "show", "dev", name).Output(); err == nil {
		parseRoute(string(out), s)
	}

	// Traffic stats from /proc/net/dev
	_ = parseNetDev(name, s)

	return s, nil
}

func parseIPAddr(output string, s *InterfaceStats) {
	for _, line := range strings.Split(output, "\n") {
		line = strings.TrimSpace(line)
		switch {
		case strings.HasPrefix(line, "inet "):
			parts := strings.Fields(line)
			if len(parts) < 2 {
				continue
			}
			cidr := parts[1]
			if slash := strings.Index(cidr, "/"); slash >= 0 {
				s.IPv4 = cidr[:slash]
				if prefix, err := strconv.Atoi(cidr[slash+1:]); err == nil {
					s.IPv4Mask = prefixToMask(prefix)
				}
			} else {
				s.IPv4 = cidr
			}
		case strings.HasPrefix(line, "inet6 "):
			parts := strings.Fields(line)
			if len(parts) >= 2 {
				s.IPv6 = append(s.IPv6, parts[1])
			}
		}
	}
}

func parseRoute(output string, s *InterfaceStats) {
	for _, line := range strings.Split(output, "\n") {
		fields := strings.Fields(strings.TrimSpace(line))
		// "default via <gw> dev ..."
		if len(fields) >= 3 && fields[0] == "default" && fields[1] == "via" {
			s.Gateway = fields[2]
			return
		}
	}
}

func parseNetDev(name string, s *InterfaceStats) error {
	f, err := os.Open("/proc/net/dev")
	if err != nil {
		return err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := scanner.Text()
		colon := strings.Index(line, ":")
		if colon < 0 {
			continue
		}
		if strings.TrimSpace(line[:colon]) != name {
			continue
		}
		// Fields after colon:
		// rx: bytes packets errs drop fifo frame compressed multicast
		// tx: bytes packets errs drop fifo colls carrier compressed
		fields := strings.Fields(line[colon+1:])
		if len(fields) >= 10 {
			s.RxBytes, _ = strconv.ParseUint(fields[0], 10, 64)
			s.RxPackets, _ = strconv.ParseUint(fields[1], 10, 64)
			s.TxBytes, _ = strconv.ParseUint(fields[8], 10, 64)
			s.TxPackets, _ = strconv.ParseUint(fields[9], 10, 64)
		}
		return nil
	}
	return scanner.Err()
}

func prefixToMask(prefix int) string {
	if prefix < 0 || prefix > 32 {
		return ""
	}
	mask := ^uint32(0) << uint(32-prefix)
	return fmt.Sprintf("%d.%d.%d.%d",
		(mask>>24)&0xFF, (mask>>16)&0xFF, (mask>>8)&0xFF, mask&0xFF)
}
