package setup

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strconv"
	"strings"

	"nano-router/auth"
	"nano-router/config"
	"nano-router/network"
)

const serviceName = "network-manager"
const servicePath = "/etc/init.d/" + serviceName
const binInstallPath = "/usr/local/bin/network-manager"

func Run() {
	reader := bufio.NewReader(os.Stdin)

	printBanner()
	fmt.Println()
	fmt.Println("  This wizard will guide you through initial router configuration.")
	fmt.Println("  Press Enter to accept the default value shown in brackets.")
	fmt.Println()

	ifaces, err := network.GetInterfaces()
	if err != nil {
		fmt.Fprintf(os.Stderr, "  [ERROR] Cannot list network interfaces: %v\n", err)
		os.Exit(1)
	}

	physicalIfaces := filterPhysicalIfaces(ifaces)
	if len(physicalIfaces) < 2 {
		fmt.Fprintf(os.Stderr, "  [ERROR] At least 2 physical interfaces are required, found %d.\n", len(physicalIfaces))
		os.Exit(1)
	}

	fmt.Println("  ── Step 1: WAN Interface ──────────────────────────────────────────")
	fmt.Println()

	wanIface := selectInterface(reader, physicalIfaces, "Select WAN interface")
	fmt.Println()

	fmt.Printf("  Configuring WAN interface: %s\n", wanIface)
	fmt.Println()

	wanMode := selectWANMode(reader)
	fmt.Println()

	var wanAddress, wanNetmask, wanGateway string
	if wanMode == "static" {
		wanStats, _ := network.GetInterfaceStats(wanIface)
		defAddr := ""
		if wanStats != nil && wanStats.IPv4 != "" {
			defAddr = wanStats.IPv4
		}
		defMask := "255.255.255.0"
		if wanStats != nil && wanStats.IPv4Mask != "" {
			defMask = wanStats.IPv4Mask
		}
		defGW := ""
		if wanStats != nil && wanStats.Gateway != "" {
			defGW = wanStats.Gateway
		}

		wanAddress = readIP(reader, "  WAN IP address", defAddr)
		wanNetmask = readNetmask(reader, "  WAN netmask", defMask)
		wanGateway = readIPOrDefault(reader, "  WAN gateway", defGW)
	}

	fmt.Println()
	fmt.Println("  ── Step 2: LAN Interface ──────────────────────────────────────────")
	fmt.Println()

	remainingIfaces := removeItem(physicalIfaces, wanIface)
	lanIface := selectInterface(reader, remainingIfaces, "Select LAN interface")
	fmt.Println()

	lanStats, _ := network.GetInterfaceStats(lanIface)

	defLanAddr := "192.168.1.1"
	defLanMask := "255.255.255.0"
	if lanStats != nil && lanStats.IPv4 != "" {
		defLanAddr = lanStats.IPv4
	}
	if lanStats != nil && lanStats.IPv4Mask != "" {
		defLanMask = lanStats.IPv4Mask
	}

	fmt.Printf("  Configuring LAN interface: %s\n", lanIface)
	fmt.Println()

	lanAddress := readIP(reader, "  LAN IP address", defLanAddr)
	lanNetmask := readNetmask(reader, "  LAN netmask", defLanMask)

	lanSubnet := computeSubnet(lanAddress, lanNetmask)

	fmt.Println()

	enableDHCP := readYesNo(reader, "  Enable DHCP server on LAN?", true)
	fmt.Println()

	var dhcpRangeStart, dhcpRangeEnd string
	var dhcpLeaseTime int
	enableDHCPPool := false
	if enableDHCP {
		defStart := incrementIP(lanAddress, 100)
		defEnd := incrementIP(lanAddress, 200)
		if lanStats != nil && lanStats.IPv4 != "" {
			defStart = incrementIP(lanStats.IPv4, 100)
			defEnd = incrementIP(lanStats.IPv4, 200)
		}

		fmt.Println("  ── DHCP Pool Configuration ─────────────────────────────────────────")
		fmt.Println()
		dhcpRangeStart = readIP(reader, "  DHCP range start", defStart)
		dhcpRangeEnd = readIP(reader, "  DHCP range end", defEnd)
		dhcpLeaseTime = readInt(reader, "  Lease time (seconds)", 86400)
		enableDHCPPool = true
		fmt.Println()
	}

	fmt.Println("  ── Step 3: Administrator Account ──────────────────────────────────")
	fmt.Println()

	adminUsername := readNonEmpty(reader, "  Administrator login", "admin")
	adminPassword := readPassword(reader, "  Administrator password")
	fmt.Println()

	fmt.Println()
	fmt.Println("  ── Generating Configuration ────────────────────────────────────────")
	fmt.Println()

	cfg := buildConfig(wanIface, wanMode, wanAddress, wanNetmask, wanGateway,
		lanIface, lanAddress, lanNetmask, lanSubnet,
		enableDHCP, enableDHCPPool, dhcpRangeStart, dhcpRangeEnd, dhcpLeaseTime,
		adminUsername, adminPassword)

	if err := config.Save(cfg); err != nil {
		fmt.Fprintf(os.Stderr, "  [ERROR] Cannot save config: %v\n", err)
		os.Exit(1)
	}
	fmt.Printf("  Configuration saved to %s\n", config.GetPath())
	fmt.Println()

	fmt.Println("  ── Installing Service ──────────────────────────────────────────────")
	fmt.Println()

	if err := installService(); err != nil {
		fmt.Fprintf(os.Stderr, "  [ERROR] Cannot install service: %v\n", err)
		os.Exit(1)
	}
	fmt.Println()

	fmt.Println("  ── Setup Complete ──────────────────────────────────────────────────")
	fmt.Println()
	fmt.Printf("  Admin panel: http://%s:8080\n", lanAddress)
	fmt.Printf("  Login:       %s\n", adminUsername)
	fmt.Printf("  Password:    %s\n", adminPassword)
	fmt.Println()
	fmt.Println("  The service has been installed and enabled for autostart.")
	fmt.Println("  Run 'rc-service network-manager start' or reboot to activate.")
	fmt.Println()
}

func printBanner() {
	fmt.Println()
	fmt.Println("  ┌──────────────────────────────────────────────────────────────────┐")
	fmt.Println("  │          Alpine Router -- Initial Setup Wizard                  │")
	fmt.Println("  └──────────────────────────────────────────────────────────────────┘")
}

func selectInterface(reader *bufio.Reader, ifaces []string, prompt string) string {
	fmt.Printf("  %s:\n", prompt)
	fmt.Println()
	for i, name := range ifaces {
		stats, _ := network.GetInterfaceStats(name)
		extra := ""
		if stats != nil {
			parts := []string{}
			if stats.IPv4 != "" {
				parts = append(parts, stats.IPv4)
			}
			if stats.State != "" {
				parts = append(parts, "["+stats.State+"]")
			}
			if len(parts) > 0 {
				extra = "  " + strings.Join(parts, " ")
			}
		}
		fmt.Printf("    %2d) %s%s\n", i+1, name, extra)
	}
	fmt.Println()

	for {
		fmt.Print("  Enter number: ")
		input := strings.TrimSpace(readLine(reader))
		num, err := strconv.Atoi(input)
		if err != nil || num < 1 || num > len(ifaces) {
			fmt.Printf("  [!] Please enter a number between 1 and %d\n", len(ifaces))
			continue
		}
		selected := ifaces[num-1]
		fmt.Printf("  Selected: %s\n", selected)
		return selected
	}
}

func selectWANMode(reader *bufio.Reader) string {
	fmt.Println("  WAN addressing mode:")
	fmt.Println()
	fmt.Println("    1) DHCP ( automatic )")
	fmt.Println("    2) Static ( manual IP configuration )")
	fmt.Println()

	for {
		fmt.Print("  Select mode (1-2) [1]: ")
		input := strings.TrimSpace(readLine(reader))
		if input == "" || input == "1" {
			fmt.Println("  Selected: DHCP")
			return "dhcp"
		}
		if input == "2" {
			fmt.Println("  Selected: Static")
			return "static"
		}
		fmt.Println("  [!] Enter 1 or 2")
	}
}

func readIP(reader *bufio.Reader, prompt, def string) string {
	for {
		promptStr := fmt.Sprintf("  %s", prompt)
		if def != "" {
			promptStr += fmt.Sprintf(" [%s]", def)
		}
		promptStr += ": "
		fmt.Print(promptStr)
		input := strings.TrimSpace(readLine(reader))
		if input == "" && def != "" {
			input = def
		}
		if net.ParseIP(input) == nil {
			fmt.Println("  [!] Invalid IP address, try again")
			continue
		}
		return input
	}
}

func readIPOrDefault(reader *bufio.Reader, prompt, def string) string {
	for {
		promptStr := fmt.Sprintf("  %s", prompt)
		if def != "" {
			promptStr += fmt.Sprintf(" [%s]", def)
		}
		promptStr += " (or leave empty to skip): "
		fmt.Print(promptStr)
		input := strings.TrimSpace(readLine(reader))
		if input == "" {
			if def != "" {
				return def
			}
			return ""
		}
		if net.ParseIP(input) == nil {
			fmt.Println("  [!] Invalid IP address, try again")
			continue
		}
		return input
	}
}

func readNetmask(reader *bufio.Reader, prompt, def string) string {
	for {
		promptStr := fmt.Sprintf("  %s", prompt)
		if def != "" {
			promptStr += fmt.Sprintf(" [%s]", def)
		}
		promptStr += ": "
		fmt.Print(promptStr)
		input := strings.TrimSpace(readLine(reader))
		if input == "" && def != "" {
			input = def
		}
		if isValidNetmask(input) {
			return input
		}
		fmt.Println("  [!] Invalid netmask, try again (e.g. 255.255.255.0)")
	}
}

func readYesNo(reader *bufio.Reader, prompt string, def bool) bool {
	defStr := "Y/n"
	if !def {
		defStr = "y/N"
	}
	for {
		fmt.Printf("  %s [%s]: ", prompt, defStr)
		input := strings.TrimSpace(strings.ToLower(readLine(reader)))
		if input == "" {
			return def
		}
		switch input {
		case "y", "yes":
			return true
		case "n", "no":
			return false
		default:
			fmt.Println("  [!] Enter y or n")
		}
	}
}

func readNonEmpty(reader *bufio.Reader, prompt, def string) string {
	for {
		promptStr := fmt.Sprintf("  %s", prompt)
		if def != "" {
			promptStr += fmt.Sprintf(" [%s]", def)
		}
		promptStr += ": "
		fmt.Print(promptStr)
		input := strings.TrimSpace(readLine(reader))
		if input == "" && def != "" {
			return def
		}
		if input != "" {
			return input
		}
		fmt.Println("  [!] Value cannot be empty")
	}
}

func readPassword(reader *bufio.Reader, prompt string) string {
	fd := int(os.Stdin.Fd())
	oldState, canMute := termiosGetState(fd)

	for {
		fmt.Printf("  %s: ", prompt)
		if canMute {
			termiosDisableEcho(fd)
		}
		pw1 := readLineRaw()
		if canMute {
			termiosRestore(fd, oldState)
			fmt.Println()
		}
		if len(pw1) < 4 {
			fmt.Println("  [!] Password must be at least 4 characters")
			continue
		}

		fmt.Print("  Confirm password: ")
		if canMute {
			termiosDisableEcho(fd)
		}
		pw2 := readLineRaw()
		if canMute {
			termiosRestore(fd, oldState)
			fmt.Println()
		}
		if pw1 != pw2 {
			fmt.Println("  [!] Passwords do not match, try again")
			continue
		}
		return pw1
	}
}

func readLineRaw() string {
	var line []byte
	buf := make([]byte, 1)
	for {
		n, err := os.Stdin.Read(buf)
		if n > 0 {
			if buf[0] == '\n' || buf[0] == '\r' {
				break
			}
			line = append(line, buf[0])
		}
		if err != nil {
			break
		}
	}
	return string(line)
}

func readInt(reader *bufio.Reader, prompt string, def int) int {
	for {
		fmt.Printf("  %s [%d]: ", prompt, def)
		input := strings.TrimSpace(readLine(reader))
		if input == "" {
			return def
		}
		num, err := strconv.Atoi(input)
		if err != nil || num <= 0 {
			fmt.Println("  [!] Enter a positive integer")
			continue
		}
		return num
	}
}

func readLine(reader *bufio.Reader) string {
	line, _ := reader.ReadString('\n')
	return strings.TrimRight(line, "\r\n")
}

func filterPhysicalIfaces(ifaces []string) []string {
	skip := map[string]bool{"lo": true}
	var result []string
	for _, name := range ifaces {
		if skip[name] {
			continue
		}
		if network.IsVLAN(name) {
			continue
		}
		result = append(result, name)
	}
	sort.Strings(result)
	return result
}

func removeItem(xs []string, item string) []string {
	var result []string
	for _, x := range xs {
		if x != item {
			result = append(result, x)
		}
	}
	return result
}

func isValidNetmask(mask string) bool {
	m := net.ParseIP(mask)
	if m == nil {
		return false
	}
	m4 := m.To4()
	if m4 == nil {
		return false
	}
	var n uint32
	for _, b := range m4 {
		n = (n << 8) | uint32(b)
	}
	if n == 0 {
		return false
	}
	complement := ^n
	return complement&((complement)+1) == 0
}

func computeSubnet(ipStr, maskStr string) string {
	ip := net.ParseIP(ipStr)
	mask := net.ParseIP(maskStr)
	if ip == nil || mask == nil {
		return ipStr
	}
	ip4 := ip.To4()
	mask4 := mask.To4()
	if ip4 == nil || mask4 == nil {
		return ipStr
	}
	ipMask := net.IPMask(mask4)
	subnet := net.IPNet{
		IP:   ip4.Mask(ipMask),
		Mask: ipMask,
	}
	return subnet.String()
}

func incrementIP(ipStr string, offset int) string {
	ip := net.ParseIP(ipStr)
	if ip == nil {
		return ipStr
	}
	ip4 := ip.To4()
	if ip4 == nil {
		return ipStr
	}
	val := uint32(ip4[0])<<24 | uint32(ip4[1])<<16 | uint32(ip4[2])<<8 | uint32(ip4[3])
	val += uint32(offset)
	return fmt.Sprintf("%d.%d.%d.%d",
		(val>>24)&0xFF, (val>>16)&0xFF, (val>>8)&0xFF, val&0xFF)
}

func buildConfig(
	wanIface, wanMode, wanAddress, wanNetmask, wanGateway,
	lanIface, lanAddress, lanNetmask, lanSubnet string,
	enableDHCP, enableDHCPPool bool,
	dhcpRangeStart, dhcpRangeEnd string,
	dhcpLeaseTime int,
	adminUsername, adminPassword string,
) *config.AppConfig {
	cfg := &config.AppConfig{
		Interfaces: map[string]*config.InterfaceConfig{
			wanIface: {
				Type:    "wan",
				Auto:    true,
				Mode:    wanMode,
				Address: wanAddress,
				Netmask: wanNetmask,
				Gateway: wanGateway,
			},
			lanIface: {
				Type:    "lan",
				Auto:    true,
				Mode:    "static",
				Address: lanAddress,
				Netmask: lanNetmask,
			},
		},
		NAT: config.NATConfig{
			Interfaces: []string{lanIface},
		},
		Firewall: config.FirewallConfig{
			VLANIsolation: true,
			Rules:         []config.FirewallRule{},
		},
		DHCP: config.DHCPConfig{
			Enabled: enableDHCP,
			Pools:   []config.DHCPPool{},
		},
		KnownDevices: []config.KnownDevice{},
		Mihomo:       config.MihomoConfig{Enabled: false},
		ClientPolicy: config.ClientPolicyConfig{Default: "direct"},
		Auth: config.AuthConfig{
			Username:     adminUsername,
			PasswordHash: auth.HashPassword(adminPassword),
		},
		ListenAddresses: []string{lanAddress},
	}

	if enableDHCP && enableDHCPPool {
		parts := strings.Split(lanSubnet, "/")
		subnetPart := lanSubnet
		if len(parts) == 2 {
			subnetPart = parts[0] + "/" + parts[1]
		}

		dnsServers := []string{lanAddress}
		if wanMode == "static" && wanGateway != "" {
			dnsServers = []string{lanAddress, wanGateway}
		}

		cfg.DHCP.Pools = append(cfg.DHCP.Pools, config.DHCPPool{
			Interface:  lanIface,
			Enabled:    true,
			Subnet:     subnetPart,
			Netmask:    lanNetmask,
			RangeStart: dhcpRangeStart,
			RangeEnd:   dhcpRangeEnd,
			Router:     lanAddress,
			DNS:        dnsServers,
			LeaseTime:  dhcpLeaseTime,
		})
	}

	return cfg
}

func installService() error {
	exe, err := os.Executable()
	if err != nil {
		return fmt.Errorf("detect executable path: %w", err)
	}
	resolved, err := filepath.EvalSymlinks(exe)
	if err != nil {
		resolved = exe
	}

	if _, err := os.Stat(servicePath); err == nil {
		fmt.Println("  Removing existing service...")
		_ = exec.Command("rc-service", serviceName, "stop").Run()
		_ = exec.Command("rc-update", "delete", serviceName).Run()
		_ = os.Remove(servicePath)
	}

	fmt.Printf("  Installing binary to %s...\n", binInstallPath)
	if err := os.MkdirAll(filepath.Dir(binInstallPath), 0755); err != nil {
		return fmt.Errorf("create bin directory: %w", err)
	}

	if out, err := exec.Command("cp", resolved, binInstallPath).CombinedOutput(); err != nil {
		return fmt.Errorf("copy binary: %s: %w", strings.TrimSpace(string(out)), err)
	}

	if err := os.Chmod(binInstallPath, 0755); err != nil {
		return fmt.Errorf("chmod binary: %w", err)
	}
	fmt.Println("  Binary installed and made executable")

	serviceContent := `#!/sbin/openrc-run

description="Network Manager Web Panel"
command="` + binInstallPath + `"
command_background=true
pidfile="/run/${RC_SVCNAME}.pid"
output_log="/var/log/network-manager.log"
error_log="/var/log/network-manager.log"

depend() {
    need net
    after firewall
}

start_pre() {
    checkpath --directory /var/log
}
`

	if err := os.WriteFile(servicePath, []byte(serviceContent), 0755); err != nil {
		return fmt.Errorf("write service file: %w", err)
	}
	fmt.Printf("  Service file created: %s\n", servicePath)

	if out, err := exec.Command("rc-update", "add", serviceName, "default").CombinedOutput(); err != nil {
		return fmt.Errorf("enable service: %s: %w", strings.TrimSpace(string(out)), err)
	}
	fmt.Println("  Service enabled for autostart (default runlevel)")

	return nil
}