package main

import (
	"embed"
	"io/fs"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"alpine-router/config"
	"alpine-router/dhcp"
	"alpine-router/firewall"
	"alpine-router/handlers"
	"alpine-router/mihomo"
	"alpine-router/nat"
	"alpine-router/network"
	"alpine-router/traffic"
)

//go:embed public
var publicFS embed.FS

func main() {
	cfg, err := config.Load()
	if err != nil {
		log.Fatalf("load config.yaml: %v", err)
	}

	firstRun := len(cfg.Interfaces) == 0 && len(cfg.NAT.Interfaces) == 0 && len(cfg.DHCP.Pools) == 0

	mihomo.SetConfigDir(filepath.Join(filepath.Dir(config.GetPath()), "mihomo"))

	if err := mihomo.EnsureDefaultConfig(); err != nil {
		log.Printf("Warning: ensure default mihomo config: %v", err)
	}

	if firstRun {
		log.Printf("First run — importing current system state into %s", config.GetPath())
		cfg = importSystemState()
		if err := config.Save(cfg); err != nil {
			log.Printf("Warning: save initial config.yaml: %v", err)
		} else {
			log.Printf("Saved initial config.yaml with %d interfaces, %d NAT, %d DHCP pools",
				len(cfg.Interfaces), len(cfg.NAT.Interfaces), len(cfg.DHCP.Pools))
		}
	} else {
		applyConfig(cfg)
	}

	mux := http.NewServeMux()

	mux.HandleFunc("/api/interfaces", handlers.HandleInterfaces)
	mux.HandleFunc("/api/interfaces/", func(w http.ResponseWriter, r *http.Request) {
		suffix := strings.TrimPrefix(r.URL.Path, "/api/interfaces/")
		if strings.Contains(suffix, "/") {
			handlers.HandleInterfaceAction(w, r)
		} else {
			handlers.HandleInterfaceSingle(w, r)
		}
	})
	mux.HandleFunc("/api/config/", handlers.HandleConfig)
	mux.HandleFunc("/api/apply", handlers.HandleApply)
	mux.HandleFunc("/api/pending", handlers.HandlePending)

	mux.HandleFunc("/api/clients", handlers.HandleClients)
	mux.HandleFunc("/api/clients/update/", handlers.HandleClientUpdate)

	mux.HandleFunc("/api/config.yaml", handlers.HandleConfigYAML)

	mux.HandleFunc("/api/firewall", handlers.HandleFirewall)
	mux.HandleFunc("/api/firewall/apply", handlers.HandleFirewallApply)

	mux.HandleFunc("/api/nat", func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case "GET":
			handlers.HandleNATGet(w, r)
		case "POST":
			handlers.HandleNATSave(w, r)
		default:
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	})

	mux.HandleFunc("/api/dhcp/status", handlers.HandleDHCPStatus)
	mux.HandleFunc("/api/dhcp/config", func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case "GET":
			handlers.HandleDHCPConfigGet(w, r)
		case "POST":
			handlers.HandleDHCPConfigSave(w, r)
		default:
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	})
	mux.HandleFunc("/api/dhcp/apply", handlers.HandleDHCPApply)

	mux.HandleFunc("/api/mihomo/status", handlers.HandleMihomoStatus)
	mux.HandleFunc("/api/mihomo/start", handlers.HandleMihomoStart)
	mux.HandleFunc("/api/mihomo/stop", handlers.HandleMihomoStop)
	mux.HandleFunc("/api/mihomo/restart", handlers.HandleMihomoRestart)
	mux.HandleFunc("/api/mihomo/config", handlers.HandleMihomoConfig)
	mux.HandleFunc("/api/mihomo/config.yaml", handlers.HandleMihomoConfigYAML)
	mux.HandleFunc("/api/mihomo/logs", handlers.HandleMihomoLogs)
	mux.HandleFunc("/api/mihomo/upload-core", handlers.HandleMihomoUploadCore)
	mux.HandleFunc("/api/mihomo/api/", handlers.HandleMihomoAPIProxy)
	mux.HandleFunc("/api/mihomo/ws/", handlers.HandleMihomoWSProxy)

	sub, err := fs.Sub(publicFS, "public")
	if err != nil {
		log.Fatal(err)
	}
	mux.Handle("/", http.FileServer(http.FS(sub)))

	port := "8080"
	if p := os.Getenv("PORT"); p != "" {
		port = p
	}

	traffic.Start()

	log.Printf("Config file: %s", config.GetPath())
	log.Printf("Network Manager listening on http://0.0.0.0:%s", port)
	log.Fatal(http.ListenAndServe(":"+port, mux))
}

func importSystemState() *config.AppConfig {
	cfg := &config.AppConfig{
		Interfaces:   map[string]*config.InterfaceConfig{},
		NAT:          config.NATConfig{Interfaces: []string{}},
		DHCP:         config.DHCPConfig{Pools: []config.DHCPPool{}},
		KnownDevices: []config.KnownDevice{},
	}

	ifaceConfigs, err := network.ParseConfig()
	if err != nil {
		log.Printf("Warning: parse /etc/network/interfaces: %v", err)
	} else {
		for name, ic := range ifaceConfigs {
			cfg.Interfaces[name] = &config.InterfaceConfig{
				Auto:    ic.Auto,
				Mode:    ic.Mode,
				Address: ic.Address,
				Netmask: ic.Netmask,
				Gateway: ic.Gateway,
				DNS:     ic.DNS,
				Extra:   ic.Extra,
			}
		}
		log.Printf("Imported %d interfaces from /etc/network/interfaces", len(cfg.Interfaces))
	}

	if nat.IsInstalled() {
		natCfg, err := nat.Load()
		if err != nil {
			log.Printf("Warning: load NAT state: %v", err)
		} else {
			cfg.NAT.Interfaces = natCfg.Interfaces
			log.Printf("Imported %d NAT interfaces", len(cfg.NAT.Interfaces))
		}
	}

	if dhcp.IsInstalled() {
		dhcpCfg, err := dhcp.Load()
		if err != nil {
			log.Printf("Warning: load DHCP state: %v", err)
		} else {
			cfg.DHCP.Enabled = dhcpCfg.Enabled
			for _, p := range dhcpCfg.Pools {
				cfg.DHCP.Pools = append(cfg.DHCP.Pools, config.DHCPPool{
					Interface:  p.Interface,
					Enabled:    p.Enabled,
					Subnet:     p.Subnet,
					Netmask:    p.Netmask,
					RangeStart: p.RangeStart,
					RangeEnd:   p.RangeEnd,
					Router:     p.Router,
					DNS:        p.DNS,
					LeaseTime:  p.LeaseTime,
				})
			}
			log.Printf("Imported DHCP config (enabled=%v, %d pools)", cfg.DHCP.Enabled, len(cfg.DHCP.Pools))
		}
	}

	return cfg
}

func applyConfig(cfg *config.AppConfig) {
	if len(cfg.Interfaces) > 0 {
		curConfigs, err := network.ParseConfig()
		if err != nil {
			log.Printf("Warning: parse current interfaces: %v", err)
			curConfigs = map[string]*network.InterfaceConfig{}
		}
		for name, iface := range cfg.Interfaces {
			ic := &network.InterfaceConfig{
				Name:    name,
				Auto:    iface.Auto,
				Mode:    iface.Mode,
				Address: iface.Address,
				Netmask: iface.Netmask,
				Gateway: iface.Gateway,
				DNS:     iface.DNS,
				Extra:   iface.Extra,
			}
			curConfigs[name] = ic
		}
		if err := network.WriteConfig(curConfigs); err != nil {
			log.Printf("Warning: write interfaces: %v", err)
		} else {
			log.Printf("Applied %d interface configs", len(cfg.Interfaces))
		}
	}

	if nat.IsInstalled() {
		natCfg := &nat.Config{Interfaces: cfg.NAT.Interfaces}
		if err := nat.Save(natCfg); err != nil {
			log.Printf("Warning: save NAT state: %v", err)
		}

		var blockedIPs []string
		for _, kd := range cfg.KnownDevices {
			if kd.Blocked {
				ip := kd.IP
				if kd.StaticIP != "" {
					ip = kd.StaticIP
				}
				if ip != "" {
					blockedIPs = append(blockedIPs, ip)
				}
			}
		}

		// Build LAN interface set: NAT interfaces + all VLAN interfaces + their parents.
		seenLAN := map[string]bool{}
		var lanIfaces []string
		addLAN := func(name string) {
			if name != "" && !seenLAN[name] {
				lanIfaces = append(lanIfaces, name)
				seenLAN[name] = true
			}
		}
		for _, name := range cfg.NAT.Interfaces {
			addLAN(name)
		}
		if names, err := network.GetInterfaces(); err == nil {
			for _, name := range names {
				if network.IsVLAN(name) {
					addLAN(name)
					addLAN(network.VLANParent(name))
				}
			}
		}

		// Convert config firewall rules.
		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,
			}
		}

		err := firewall.ApplyAll(
			firewall.NATConfig{Interfaces: cfg.NAT.Interfaces},
			firewall.Config{Rules: fwRules, VLANIsolation: cfg.Firewall.VLANIsolation},
			blockedIPs,
			lanIfaces,
		)
		if err != nil {
			log.Printf("Warning: apply firewall/NAT rules: %v", err)
		} else {
			log.Printf("Firewall/NAT applied (%d NAT ifaces, %d fw rules, %d blocked, vlan_isolation=%v)",
				len(cfg.NAT.Interfaces), len(fwRules), len(blockedIPs), cfg.Firewall.VLANIsolation)
		}
	} else {
		log.Printf("nftables not installed — NAT/firewall unavailable (install with: apk add nftables)")
	}

	if dhcp.IsInstalled() {
		dhcpCfg := &dhcp.Config{
			Enabled: cfg.DHCP.Enabled,
			Pools:   make([]dhcp.Pool, len(cfg.DHCP.Pools)),
		}
		for i, p := range cfg.DHCP.Pools {
			dhcpCfg.Pools[i] = dhcp.Pool{
				Interface:  p.Interface,
				Enabled:    p.Enabled,
				Subnet:     p.Subnet,
				Netmask:    p.Netmask,
				RangeStart: p.RangeStart,
				RangeEnd:   p.RangeEnd,
				Router:     p.Router,
				DNS:        p.DNS,
				LeaseTime:  p.LeaseTime,
			}
		}

		var dhcpBindings []dhcp.StaticBinding
		for _, kd := range cfg.KnownDevices {
			if kd.StaticIP != "" && kd.MAC != "" {
				dhcpBindings = append(dhcpBindings, dhcp.StaticBinding{
					MAC:  kd.MAC,
					Host: kd.Hostname,
					IP:   kd.StaticIP,
				})
			}
		}

		if err := dhcp.Save(dhcpCfg); err != nil {
			log.Printf("Warning: save DHCP state: %v", err)
		}
		if err := dhcp.WriteConfigsWithBindings(dhcpCfg, dhcpBindings); err != nil {
			log.Printf("Warning: write dnsmasq config: %v", err)
		} else {
			log.Printf("DHCP config written (%d pools, %d static bindings)", len(dhcpCfg.Pools), len(dhcpBindings))
		}
		if dhcpCfg.Enabled {
			if err := dhcp.ServiceRestart(); err != nil {
				log.Printf("Warning: start dnsmasq: %v", err)
			} else {
				log.Printf("dnsmasq started")
			}
		}
	} else {
		log.Printf("dnsmasq not installed — DHCP unavailable (install with: apk add dnsmasq)")
	}
}