package handlers

import (
	"log"

	"nano-router/clients"
	"nano-router/config"
	"nano-router/firewall"
	"nano-router/nat"
	"nano-router/network"
)

// resolveClientPolicy returns the effective routing policy for a device.
// Explicit per-device Policy takes priority; then legacy Blocked flag; then default.
func resolveClientPolicy(kd config.KnownDevice, defaultPolicy string) string {
	if kd.Policy != "" {
		return kd.Policy
	}
	if kd.Blocked {
		return "disabled"
	}
	if defaultPolicy != "" {
		return defaultPolicy
	}
	return "direct"
}

// applyAllRules rebuilds the complete nftables ruleset from the current config:
// NAT masquerade + tproxy for VPN clients + disabled client drops +
// user firewall rules + VLAN isolation.
func applyAllRules(cfg *config.AppConfig) error {
	if !nat.IsInstalled() {
		return nil
	}

	defaultPolicy := cfg.ClientPolicy.Default
	if defaultPolicy == "" {
		defaultPolicy = "direct"
	}

	// Classify each known device into disabled or vpn buckets.
	// For devices connected on multiple interfaces (same MAC, different IPs)
	// we also include all live ARP IPs so every interface gets the same policy.
	arpByMAC := clients.GetARPIPsByMAC()
	seenIP := make(map[string]bool)
	var disabledIPs, vpnIPs []string

	addIP := func(ip, policy string) {
		if ip == "" || seenIP[ip] {
			return
		}
		seenIP[ip] = true
		switch policy {
		case "disabled":
			disabledIPs = append(disabledIPs, ip)
		case "vpn":
			vpnIPs = append(vpnIPs, ip)
		}
	}

	for _, kd := range cfg.KnownDevices {
		policy := resolveClientPolicy(kd, defaultPolicy)
		// Primary stored IP.
		ip := kd.IP
		if kd.StaticIP != "" {
			ip = kd.StaticIP
		}
		addIP(ip, policy)
		// All other IPs this MAC currently has in the ARP table.
		for _, arpIP := range arpByMAC[kd.MAC] {
			addIP(arpIP, policy)
		}
	}

	// Build the LAN interface set for isolation:
	// all NAT interfaces + all VLAN interfaces (active + pending).
	seen := map[string]bool{}
	var lanIfaces []string
	addLAN := func(name string) {
		if name != "" && !seen[name] {
			lanIfaces = append(lanIfaces, name)
			seen[name] = true
		}
	}
	for _, name := range cfg.NAT.Interfaces {
		addLAN(name)
	}
	names, _ := network.GetInterfaces()
	for _, name := range names {
		if network.IsVLAN(name) {
			addLAN(name)
			addLAN(network.VLANParent(name))
		}
	}
	for name := range network.GetAllPending() {
		if network.IsVLAN(name) {
			addLAN(name)
			addLAN(network.VLANParent(name))
		}
	}

	// Convert config.FirewallRule → firewall.Rule.
	fwRules := make([]firewall.Rule, len(cfg.Firewall.Rules))
	for i, r := range cfg.Firewall.Rules {
		fwRules[i] = firewall.Rule{
			ID:       r.ID,
			Enabled:  r.Enabled,
			Action:   r.Action,
			Protocol: r.Protocol,
			SrcAddr:  r.SrcAddr,
			SrcPort:  r.SrcPort,
			DstAddr:  r.DstAddr,
			DstPort:  r.DstPort,
			InIface:  r.InIface,
			OutIface: r.OutIface,
			Comment:  r.Comment,
		}
	}

	return firewall.ApplyAll(
		firewall.NATConfig{Interfaces: cfg.NAT.Interfaces},
		firewall.Config{Rules: fwRules, VLANIsolation: cfg.Firewall.VLANIsolation},
		lanIfaces,
		firewall.ClientPolicies{
			DisabledIPs: disabledIPs,
			VPNIPs:      vpnIPs,
		},
	)
}

// applyBlockedFirewall is the async helper called after client or policy updates.
func applyBlockedFirewall() {
	cfg, err := config.Load()
	if err != nil {
		log.Printf("Warning: load config for firewall: %v", err)
		return
	}
	if err := applyAllRules(cfg); err != nil {
		log.Printf("Warning: apply firewall rules: %v", err)
	}
}
