package wol

import (
	"encoding/hex"
	"fmt"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
)

// Service kapselt WOL-Einstellungen und -Verhalten
type Service struct {
	broadcastAddr string
	port          int
	debug         bool
}

// NewService erstellt einen neuen Wake-on-LAN Service und liest Konfiguration
// Konfiguration per Env:
// - WOL_BROADCAST: explizite Broadcast-Adresse (z. B. 192.168.0.255). Wenn leer, werden Interface-Broadcasts verwendet
// - WOL_PORT: Ziel-UDP-Port (Standard 9)
// - WOL_DEBUG: bei "1", "true" (case-insensitive) werden Debug-Logs ausgegeben
func NewService() *Service {
	bcast := strings.TrimSpace(os.Getenv("WOL_BROADCAST"))
	port := 9
	if v := strings.TrimSpace(os.Getenv("WOL_PORT")); v != "" {
		if p, err := strconv.Atoi(v); err == nil && p > 0 && p <= 65535 {
			port = p
		}
	}
	debug := false
	if v := strings.ToLower(strings.TrimSpace(os.Getenv("WOL_DEBUG"))); v == "1" || v == "true" || v == "yes" {
		debug = true
	}
	return &Service{broadcastAddr: bcast, port: port, debug: debug}
}

// WakePC sendet ein Wake-on-LAN Paket an die angegebene MAC-Adresse
func (s *Service) WakePC(mac string) error {
	cleanMAC := strings.ReplaceAll(strings.ReplaceAll(mac, ":", ""), "-", "")
	if s.debug {
		log.Printf("WOL: preparing magic packet for MAC %s (clean %s)", mac, cleanMAC)
	}

	magicPacket, err := s.createMagicPacket(cleanMAC)
	if err != nil {
		log.Printf("Fehler beim Erstellen des Magic Packets: %v", err)
		return err
	}
	if s.debug {
		preview := 12
		if len(magicPacket) < preview {
			preview = len(magicPacket)
		}
		log.Printf("WOL: packet size=%d preview=%s...", len(magicPacket), strings.ToUpper(hex.EncodeToString(magicPacket[:preview])))
	}

	if s.broadcastAddr != "" {
		// Explizite Broadcast-Adresse verwenden
		addr := fmt.Sprintf("%s:%d", s.broadcastAddr, s.port)
		if s.debug {
			log.Printf("WOL: sending to explicit broadcast %s", addr)
		}
		return s.sendViaUDP(magicPacket, nil, s.broadcastAddr)
	}

	// Über alle aktiven Interfaces senden (IPv4 Broadcast)
	broadcasts := s.collectInterfaceBroadcasts()
	if len(broadcasts) == 0 {
		// Fallback: Limited Broadcast
		if s.debug {
			log.Printf("WOL: no interface broadcasts found, falling back to 255.255.255.255")
		}
		return s.sendViaUDP(magicPacket, nil, "255.255.255.255")
	}

	var firstErr error
	var sent int
	for _, it := range broadcasts {
		laddr := &net.UDPAddr{IP: it.localIP, Port: 0}
		if s.debug {
			log.Printf("WOL: sending from %s to %s:%d", laddr.IP.String(), it.broadcast.String(), s.port)
		}
		err := s.sendViaUDP(magicPacket, laddr, it.broadcast.String())
		if err != nil {
			if firstErr == nil {
				firstErr = err
			}
			if s.debug {
				log.Printf("WOL: send failed on iface %s → %s: %v", laddr.IP.String(), it.broadcast.String(), err)
			}
			continue
		}
		sent++
	}
	if sent == 0 && firstErr != nil {
		return firstErr
	}
	if s.debug {
		log.Printf("WOL: successfully sent on %d interface(s)", sent)
	}
	return nil
}

// helper zum Senden über UDP4 (optional gebunden an LocalAddr)
func (s *Service) sendViaUDP(packet []byte, laddr *net.UDPAddr, bcast string) error {
	raddr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", bcast, s.port))
	if err != nil {
		return err
	}
	conn, err := net.DialUDP("udp4", laddr, raddr)
	if err != nil {
		return err
	}
	defer conn.Close()

	// Broadcast explizit aktivieren (OS-spezifisch)
	if err := enableBroadcast(conn, s.debug); err != nil {
		if s.debug {
			log.Printf("WOL: enable broadcast failed: %v", err)
		}
	}

	// Schreiben
	n, err := conn.Write(packet)
	if s.debug {
		log.Printf("WOL: wrote %d bytes to %s", n, raddr.String())
	}
	return err
}

// createMagicPacket erstellt ein Wake-on-LAN Magic Packet
func (s *Service) createMagicPacket(mac string) ([]byte, error) {
	macBytes, err := hex.DecodeString(mac)
	if err != nil {
		return nil, err
	}
	if len(macBytes) != 6 {
		return nil, fmt.Errorf("ungültige MAC-Länge: %d", len(macBytes))
	}
	packet := make([]byte, 102)
	for i := 0; i < 6; i++ {
		packet[i] = 0xFF
	}
	for i := 6; i < 102; i += 6 {
		copy(packet[i:i+6], macBytes)
	}
	return packet, nil
}

// ifaceBroadcast repräsentiert eine Broadcast-Adresse plus lokale IP
type ifaceBroadcast struct {
	localIP   net.IP
	broadcast net.IP
}

// collectInterfaceBroadcasts ermittelt Broadcast-Adressen für alle aktiven IPv4-Interfaces
func (s *Service) collectInterfaceBroadcasts() []ifaceBroadcast {
	var result []ifaceBroadcast
	ifs, err := net.Interfaces()
	if err != nil {
		if s.debug {
			log.Printf("WOL: failed to list interfaces: %v", err)
		}
		return result
	}
	for _, iface := range ifs {
		if (iface.Flags&net.FlagUp) == 0 || (iface.Flags&net.FlagLoopback) != 0 {
			continue
		}
		addrs, err := iface.Addrs()
		if err != nil {
			continue
		}
		for _, a := range addrs {
			ipNet, ok := a.(*net.IPNet)
			if !ok || ipNet.IP == nil {
				continue
			}
			ip4 := ipNet.IP.To4()
			if ip4 == nil {
				continue
			}
			mask := ipNet.Mask
			if len(mask) != 4 {
				continue
			}
			bcast := net.IPv4(
				ip4[0]|^mask[0],
				ip4[1]|^mask[1],
				ip4[2]|^mask[2],
				ip4[3]|^mask[3],
			)
			result = append(result, ifaceBroadcast{localIP: ip4, broadcast: bcast})
		}
	}
	return result
}

// ValidateMAC prüft, ob die MAC-Adresse gültig ist
func ValidateMAC(mac string) bool {
	cleanMAC := strings.ReplaceAll(strings.ReplaceAll(mac, ":", ""), "-", "")
	if len(cleanMAC) != 12 {
		return false
	}
	_, err := hex.DecodeString(cleanMAC)
	return err == nil
}
