package collector

import (
	"encoding/json"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

type DHCPLease struct {
	IP       string `json:"ip"`
	MAC      string `json:"mac"`
	Hostname string `json:"hostname"`
	Start    string `json:"start,omitempty"`
	End      string `json:"end,omitempty"`
	Status   string `json:"status"` // "active", "free", "backup"
	Pool     string `json:"pool,omitempty"`
}

type DHCPInfo struct {
	Server string      `json:"server"` // "kea", "isc-dhcpd", "dnsmasq", "none"
	Leases []DHCPLease `json:"leases"`
}

func CollectDHCP() DHCPInfo {
	// 1. Versuch: KEA via configctl (OPNsense 24.7+)
	if info := tryKEA(); info != nil {
		return *info
	}

	// 2. Versuch: ISC dhcpd Lease-Datei
	if info := tryISCDhcpd(); info != nil {
		return *info
	}

	// 3. Versuch: dnsmasq Leases
	if info := tryDnsmasqLeases(); info != nil {
		return *info
	}

	return DHCPInfo{Server: "none"}
}

// tryKEA versucht KEA-Leases ueber configctl abzufragen
func tryKEA() *DHCPInfo {
	out, err := exec.Command("/usr/local/sbin/configctl", "kea", "list", "leases4").Output()
	if err != nil {
		return nil
	}

	// JSON-Antwort: {"records": [{"address": "...", "hwaddr": "...", "hostname": "...", "state": "0", "expire": "1772261642", ...}]}
	var result struct {
		Records []struct {
			Address   string `json:"address"`
			HWAddr    string `json:"hwaddr"`
			HWAddress string `json:"hw_address"` // Fallback fuer aeltere KEA-Versionen
			Hostname  string `json:"hostname"`
			State     string `json:"state"`
			SubnetID  string `json:"subnet_id"`
			Expire    string `json:"expire"`
			ClientID  string `json:"client_id"`
		} `json:"records"`
	}

	if err := json.Unmarshal(out, &result); err != nil {
		return nil
	}

	info := &DHCPInfo{Server: "kea"}
	for _, r := range result.Records {
		status := "active"
		if r.State == "1" {
			status = "declined"
		} else if r.State == "2" {
			status = "expired"
		}

		mac := r.HWAddr
		if mac == "" {
			mac = r.HWAddress
		}

		// Expire-Timestamp in lesbares Datum umwandeln
		expireStr := r.Expire
		if ts, err := strconv.ParseInt(r.Expire, 10, 64); err == nil && ts > 0 {
			expireStr = time.Unix(ts, 0).Format("2006-01-02 15:04:05")
		}

		info.Leases = append(info.Leases, DHCPLease{
			IP:       r.Address,
			MAC:      mac,
			Hostname: r.Hostname,
			End:      expireStr,
			Status:   status,
		})
	}
	return info
}

// tryISCDhcpd parst ISC dhcpd Lease-Dateien
func tryISCDhcpd() *DHCPInfo {
	// Gaengige Pfade auf OPNsense/FreeBSD
	paths := []string{
		"/var/dhcpd/var/db/dhcpd.leases",
		"/var/db/dhcpd.leases",
	}

	for _, path := range paths {
		out, err := exec.Command("cat", path).Output()
		if err != nil || len(out) == 0 {
			continue
		}
		leases := parseISCLeases(string(out))
		if len(leases) > 0 {
			return &DHCPInfo{Server: "isc-dhcpd", Leases: leases}
		}
	}
	return nil
}

// parseISCLeases parst das ISC dhcpd.leases Format
func parseISCLeases(content string) []DHCPLease {
	var leases []DHCPLease
	var current *DHCPLease

	for _, line := range strings.Split(content, "\n") {
		line = strings.TrimSpace(line)

		if strings.HasPrefix(line, "lease ") && strings.HasSuffix(line, "{") {
			ip := strings.TrimSuffix(strings.TrimPrefix(line, "lease "), " {")
			current = &DHCPLease{IP: strings.TrimSpace(ip), Status: "active"}
			continue
		}

		if current == nil {
			continue
		}

		if line == "}" {
			leases = append(leases, *current)
			current = nil
			continue
		}

		line = strings.TrimSuffix(line, ";")

		if strings.HasPrefix(line, "starts ") {
			parts := strings.SplitN(line, " ", 3)
			if len(parts) >= 3 {
				current.Start = parts[2]
			}
		} else if strings.HasPrefix(line, "ends ") {
			parts := strings.SplitN(line, " ", 3)
			if len(parts) >= 3 {
				current.End = parts[2]
			}
		} else if strings.HasPrefix(line, "hardware ethernet ") {
			current.MAC = strings.TrimPrefix(line, "hardware ethernet ")
		} else if strings.HasPrefix(line, "client-hostname ") {
			current.Hostname = strings.Trim(strings.TrimPrefix(line, "client-hostname "), "\"")
		} else if strings.HasPrefix(line, "binding state ") {
			current.Status = strings.TrimPrefix(line, "binding state ")
		}
	}

	return leases
}

// tryDnsmasqLeases sucht dnsmasq-Lease-Dateien
func tryDnsmasqLeases() *DHCPInfo {
	paths := []string{
		"/var/db/dnsmasq.leases",
		"/tmp/dnsmasq.leases",
		"/var/run/dnsmasq.leases",
	}

	for _, path := range paths {
		out, err := exec.Command("cat", path).Output()
		if err != nil || len(out) == 0 {
			continue
		}
		leases := parseDnsmasqLeases(string(out))
		if len(leases) > 0 {
			return &DHCPInfo{Server: "dnsmasq", Leases: leases}
		}
	}
	return nil
}

// parseDnsmasqLeases parst dnsmasq lease Format: timestamp mac ip hostname clientid
func parseDnsmasqLeases(content string) []DHCPLease {
	var leases []DHCPLease
	for _, line := range strings.Split(strings.TrimSpace(content), "\n") {
		fields := strings.Fields(line)
		if len(fields) < 4 {
			continue
		}
		leases = append(leases, DHCPLease{
			MAC:      fields[1],
			IP:       fields[2],
			Hostname: fields[3],
			Status:   "active",
		})
	}
	return leases
}
