package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"
)

func findAvailableVMID(config *WindowsConfig) (int, error) {
	sshCmd := func(args ...string) *exec.Cmd {
		fullArgs := []string{
			"-o", "StrictHostKeyChecking=no",
			"-o", "UserKnownHostsFile=/dev/null",
			fmt.Sprintf("%s@%s", strings.Split(config.ProxmoxUser, "@")[0], config.ProxmoxHost),
		}
		fullArgs = append(fullArgs, args...)
		return exec.Command("ssh", fullArgs...)
	}

	cmd := sshCmd("qm", "list")
	var stdout bytes.Buffer
	cmd.Stdout = &stdout

	if err := cmd.Run(); err != nil {
		return 0, fmt.Errorf("failed to list VMs: %w", err)
	}

	usedIDs := make(map[int]bool)
	lines := strings.Split(stdout.String(), "\n")
	for _, line := range lines[1:] {
		fields := strings.Fields(line)
		if len(fields) > 0 {
			if vmid, err := strconv.Atoi(fields[0]); err == nil {
				usedIDs[vmid] = true
			}
		}
	}

	for vmid := 10000; vmid < 20000; vmid++ {
		if !usedIDs[vmid] {
			return vmid, nil
		}
	}

	return 0, fmt.Errorf("no available VM ID found in range 10000-19999")
}

func buildNetworkConfig(config *WindowsConfig) string {
	netConfig := fmt.Sprintf("virtio,bridge=%s", config.Bridge)
	if config.VlanTag > 0 {
		netConfig += fmt.Sprintf(",tag=%d", config.VlanTag)
	}
	if config.Firewall {
		netConfig += ",firewall=1"
	}
	return netConfig
}

func createProxmoxWindowsVM(config *WindowsConfig) error {
	fmt.Println("Creating Proxmox Windows template...")

	sshCmd := func(args ...string) *exec.Cmd {
		fullArgs := []string{
			"-o", "StrictHostKeyChecking=no",
			"-o", "UserKnownHostsFile=/dev/null",
			fmt.Sprintf("%s@%s", strings.Split(config.ProxmoxUser, "@")[0], config.ProxmoxHost),
		}
		fullArgs = append(fullArgs, args...)
		return exec.Command("ssh", fullArgs...)
	}

	scpCmd := func(src, dst string) *exec.Cmd {
		return exec.Command("scp",
			"-o", "StrictHostKeyChecking=no",
			"-o", "UserKnownHostsFile=/dev/null",
			src,
			fmt.Sprintf("%s@%s:%s", strings.Split(config.ProxmoxUser, "@")[0], config.ProxmoxHost, dst),
		)
	}

	imageName := fmt.Sprintf("vm-%d-disk-0.qcow2", config.VMID)
	remotePath := fmt.Sprintf("/tmp/%s", imageName)

	if _, err := os.Stat(config.ImagePath); os.IsNotExist(err) {
		return fmt.Errorf("Windows image not found at %s", config.ImagePath)
	}

	fmt.Println("Uploading Windows image to Proxmox host...")
	maxRetries := 3
	var uploadErr error
	for attempt := 1; attempt <= maxRetries; attempt++ {
		if attempt > 1 {
			fmt.Printf("Retry upload attempt %d/%d...\n", attempt, maxRetries)
		}

		cmd := scpCmd(config.ImagePath, remotePath)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		uploadErr = cmd.Run()

		if uploadErr == nil {
			fmt.Println("Upload completed!")
			break
		}

		if attempt == maxRetries {
			return fmt.Errorf("failed to upload image after %d attempts: %w", maxRetries, uploadErr)
		}
		fmt.Printf("Upload failed: %v\n", uploadErr)
	}

	fmt.Println("Creating Windows VM and converting to template...")

	createCmd := []string{"qm", "create", fmt.Sprintf("%d", config.VMID),
		"--name", config.VMName,
		"--memory", fmt.Sprintf("%d", config.Memory),
		"--cores", fmt.Sprintf("%d", config.Cores),
		"--sockets", fmt.Sprintf("%d", config.Sockets),
		"--cpu", "host",
		"--ostype", "win11",
		"--net0", buildNetworkConfig(config),
	}

	fmt.Printf("Running: %s\n", strings.Join(createCmd, " "))
	cmd := sshCmd(createCmd...)
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("failed to create VM: %w", err)
	}

	importCmd := []string{"qm", "importdisk", fmt.Sprintf("%d", config.VMID), remotePath, config.Storage}
	fmt.Printf("Running: %s\n", strings.Join(importCmd, " "))

	cmd = sshCmd(importCmd...)
	var importOut bytes.Buffer
	cmd.Stdout = &importOut
	cmd.Stderr = &importOut

	if err := cmd.Run(); err != nil {
		fmt.Println(importOut.String())
		return fmt.Errorf("failed to import disk: %w", err)
	}

	output := importOut.String()
	fmt.Println(output)

	var diskPath string
	lines := strings.Split(output, "\n")
	for _, line := range lines {
		if strings.Contains(line, "successfully imported disk") {
			start := strings.Index(line, "'")
			end := strings.LastIndex(line, "'")
			if start != -1 && end != -1 && end > start {
				diskPath = line[start+1 : end]
				break
			}
		}
	}

	if diskPath == "" {
		return fmt.Errorf("failed to parse disk path from importdisk output")
	}

	fmt.Printf("Imported disk path: %s\n", diskPath)

	commands := [][]string{
		{"qm", "set", fmt.Sprintf("%d", config.VMID),
			"--scsihw", "virtio-scsi-pci",
			"--scsi0", diskPath,
		},
		{"qm", "set", fmt.Sprintf("%d", config.VMID),
			"--boot", "order=scsi0",
		},
		{"qm", "set", fmt.Sprintf("%d", config.VMID),
			"--machine", "q35",
		},
		{"qm", "set", fmt.Sprintf("%d", config.VMID),
			"--bios", "ovmf",
		},
		{"qm", "set", fmt.Sprintf("%d", config.VMID),
			"--efidisk0", fmt.Sprintf("%s:1,efitype=4m,pre-enrolled-keys=1", config.Storage),
		},
		{"qm", "set", fmt.Sprintf("%d", config.VMID),
			"--tpmstate0", fmt.Sprintf("%s:1,version=v2.0", config.Storage),
		},
	}

	if config.GuestAgent {
		commands = append(commands, []string{"qm", "set", fmt.Sprintf("%d", config.VMID), "--agent", "enabled=1"})
	}

	commands = append(commands, [][]string{
		{"qm", "template", fmt.Sprintf("%d", config.VMID)},
		{"rm", "-f", remotePath},
	}...)

	for _, cmdArgs := range commands {
		fmt.Printf("Running: %s\n", strings.Join(cmdArgs, " "))

		var cmdErr error
		for attempt := 1; attempt <= maxRetries; attempt++ {
			if attempt > 1 {
				fmt.Printf("Retry command attempt %d/%d...\n", attempt, maxRetries)
			}

			cmd := sshCmd(cmdArgs...)
			var stdout, stderr bytes.Buffer
			cmd.Stdout = &stdout
			cmd.Stderr = &stderr

			cmdErr = cmd.Run()

			if cmdErr == nil {
				if stdout.Len() > 0 {
					fmt.Println(stdout.String())
				}
				break
			}

			fmt.Println(stdout.String())
			fmt.Println(stderr.String())

			if attempt == maxRetries {
				return fmt.Errorf("failed to execute command '%s' after %d attempts: %w", strings.Join(cmdArgs, " "), maxRetries, cmdErr)
			}

			fmt.Printf("Command failed: %v\n", cmdErr)
		}
	}

	if config.Firewall && len(config.FirewallRules) > 0 {
		if err := configureFirewallRules(config, sshCmd); err != nil {
			return fmt.Errorf("failed to configure firewall rules: %w", err)
		}
	}

	fmt.Printf("\nWindows Template %s (ID: %d) created successfully!\n", config.VMName, config.VMID)
	fmt.Printf("You can clone it with: qm clone %d <new-vm-id> --name <new-vm-name>\n", config.VMID)

	return nil
}

func configureFirewallRules(config *WindowsConfig, sshCmd func(args ...string) *exec.Cmd) error {
	fmt.Println("Configuring firewall rules...")

	firewallConfig := "[OPTIONS]\nenable: 1\n\n[RULES]\n"

	for _, rule := range config.FirewallRules {
		ruleLine := fmt.Sprintf("%s %s", strings.ToUpper(rule.Type), strings.ToUpper(rule.Action))

		if rule.Protocol != "" {
			ruleLine += fmt.Sprintf(" -p %s", rule.Protocol)
		}
		if rule.Dport != "" {
			ruleLine += fmt.Sprintf(" -dport %s", rule.Dport)
		}
		if rule.Sport != "" {
			ruleLine += fmt.Sprintf(" -sport %s", rule.Sport)
		}
		if rule.Source != "" {
			ruleLine += fmt.Sprintf(" -source %s", rule.Source)
		}
		if rule.Dest != "" {
			ruleLine += fmt.Sprintf(" -dest %s", rule.Dest)
		}
		if rule.Comment != "" {
			ruleLine += fmt.Sprintf(" -log nolog # %s", rule.Comment)
		}

		firewallConfig += ruleLine + "\n"
	}

	firewallPath := fmt.Sprintf("/etc/pve/firewall/%d.fw", config.VMID)

	createCmd := sshCmd("bash", "-c", fmt.Sprintf("cat > %s << 'EOF'\n%sEOF", firewallPath, firewallConfig))
	var stdout, stderr bytes.Buffer
	createCmd.Stdout = &stdout
	createCmd.Stderr = &stderr

	if err := createCmd.Run(); err != nil {
		fmt.Println(stdout.String())
		fmt.Println(stderr.String())
		return fmt.Errorf("failed to create firewall config: %w", err)
	}

	fmt.Printf("Firewall rules configured: %s\n", firewallPath)
	return nil
}

func listAvailableStorage(config *WindowsConfig) error {
	fmt.Printf("Detecting available storage on %s...\n", config.ProxmoxHost)

	sshCmd := func(args ...string) *exec.Cmd {
		fullArgs := []string{
			"-o", "StrictHostKeyChecking=no",
			"-o", "UserKnownHostsFile=/dev/null",
			fmt.Sprintf("%s@%s", strings.Split(config.ProxmoxUser, "@")[0], config.ProxmoxHost),
		}
		fullArgs = append(fullArgs, args...)
		return exec.Command("ssh", fullArgs...)
	}

	cmd := sshCmd("pvesm", "status", "--output-format", "json")
	var stdout bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("failed to get storage status: %w", err)
	}

	var storageList []map[string]interface{}
	if err := json.Unmarshal(stdout.Bytes(), &storageList); err != nil {
		return fmt.Errorf("failed to parse storage status: %w", err)
	}

	fmt.Println("\nAvailable storage:")
	fmt.Println("================")

	found := false
	for _, storage := range storageList {
		nameVal, ok := storage["storage"]
		if !ok {
			continue
		}
		name, _ := nameVal.(string)

		typeVal, _ := storage["type"]
		typeStr, _ := typeVal.(string)

		activeVal, _ := storage["active"]
		activeFloat, _ := activeVal.(float64)

		if activeFloat == 1 {
			fmt.Printf("- %s (%s) - ACTIVE\n", name, typeStr)
			found = true
		}
	}

	if !found {
		fmt.Println("No active storage found!")
		fmt.Println("\nManual check:")
		fmt.Println("  ssh root@your-proxmox 'pvesm status'")
	}

	return nil
}
