//go:build windows

package apps

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	"golang.org/x/sys/windows/registry"
)

// ScanApps returns a list of installed applications via Registry
func ScanApps() ([]AppInfo, error) {
	var apps []AppInfo

	// Keys to search
	// HKLM Software\Microsoft\Windows\CurrentVersion\Uninstall
	// HKLM Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
	// HKCU Software\Microsoft\Windows\CurrentVersion\Uninstall

	keys := []struct {
		hive registry.Key
		path string
	}{
		{registry.LOCAL_MACHINE, `Software\Microsoft\Windows\CurrentVersion\Uninstall`},
		{registry.LOCAL_MACHINE, `Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall`},
		{registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Uninstall`},
	}

	seen := make(map[string]bool)

	for _, k := range keys {
		baseKey, err := registry.OpenKey(k.hive, k.path, registry.READ)
		if err != nil {
			continue
		}

		subkeys, err := baseKey.ReadSubKeyNames(-1)
		baseKey.Close()
		if err != nil {
			continue
		}

		for _, subkeyName := range subkeys {
			appKey, err := registry.OpenKey(k.hive, k.path+`\`+subkeyName, registry.READ)
			if err != nil {
				continue
			}

			displayName, _, err := appKey.GetStringValue("DisplayName")
			if err != nil || displayName == "" {
				appKey.Close()
				continue
			}

			// Define installLocation explicitly
			installLocation, _, _ := appKey.GetStringValue("InstallLocation")
			uninstallString, _, _ := appKey.GetStringValue("UninstallString")
			quietUninstallString, _, _ := appKey.GetStringValue("QuietUninstallString")

			if uninstallString == "" && quietUninstallString != "" {
				uninstallString = quietUninstallString
			}

			// Debug Log
			if strings.Contains(displayName, "Foxit") {
				fmt.Printf("found Foxit: %s | UninstallString: %s\n", displayName, uninstallString)
			}

			// Deduplication: If we've seen this Name + Location combination, skip it.
			// This handles the common case of 32-bit apps appearing in both HKLM and WOW6432Node.
			dedupKey := displayName + "|" + strings.ToLower(installLocation)
			if seen[dedupKey] {
				appKey.Close()
				continue
			}
			seen[dedupKey] = true

			// Try to get size from registry (EstimatedSize is in KB)
			sizeVal, _, errSize := appKey.GetIntegerValue("EstimatedSize")
			var sizeBytes int64
			if errSize == nil {
				sizeBytes = int64(sizeVal) * 1024
			}

			// Construct Full Registry Key Path as BundleID for later use
			hiveName := "HKLM"
			if k.hive == registry.CURRENT_USER {
				hiveName = "HKCU"
			}
			fullRegPath := hiveName + `\` + k.path + `\` + subkeyName

			apps = append(apps, AppInfo{
				Name:            displayName,
				Path:            installLocation,
				BundleID:        fullRegPath,
				UninstallString: uninstallString,
				Size:            sizeBytes,
			})
			appKey.Close()
		}
	}

	return apps, nil
}

// GetAppDetails finds all associated files (simplified for Windows)
func GetAppDetails(appPath, bundleID string) (*AppDetails, error) {
	// appPath might come from ScanApps which set it to InstallLocation.
	// bundleID is used as the Registry Key Path.

	// Re-construct basic info
	info := AppInfo{
		Name:     filepath.Base(appPath),
		Path:     appPath,
		BundleID: bundleID,
		// UninstallString is hard to recover if not passed, but usually we call GetAppDetails after ScanApps which has it.
		// For now leave empty, or we'd need to re-query registry if bundleID is a registry path.
		Size: 0,
	}

	if appPath == "" && bundleID != "" {
		// Fallback name if path is empty
		parts := strings.Split(bundleID, `\`)
		if len(parts) > 0 {
			info.Name = parts[len(parts)-1]
		}
	}

	details := &AppDetails{
		AppInfo:   info,
		TotalSize: 0,
	}

	// 1. Scan File System
	if appPath != "" {
		var size int64
		filepath.WalkDir(appPath, func(_ string, d os.DirEntry, err error) error {
			if err == nil && !d.IsDir() {
				i, _ := d.Info()
				size += i.Size()
			}
			return nil
		})

		details.AppInfo.Size = size
		details.TotalSize = size

		// Add the main folder as associated data
		details.Associated = append(details.Associated, AssociatedFile{
			Path: appPath,
			Type: "data",
			Size: size,
		})
	}

	// 2. Add Registry Key (Uninstall Entry)
	if bundleID != "" && (strings.HasPrefix(bundleID, "HKLM") || strings.HasPrefix(bundleID, "HKCU")) {
		// We treat the registry key as a "file" with special type and 0 size
		details.Associated = append(details.Associated, AssociatedFile{
			Path: "REG:" + bundleID,
			Type: "registry", // New type
			Size: 0,          // Registry entries are negligible in size
		})
	}

	return details, nil
}

// DeleteFiles removes the requested paths
func DeleteFiles(paths []string) error {
	for _, p := range paths {
		if p == "" {
			continue
		}

		// Registry Deletion
		if strings.HasPrefix(p, "REG:") {
			regPath := strings.TrimPrefix(p, "REG:")
			deleteRegistryKey(regPath)
			continue
		}

		// Safety checks
		if p == "C:\\" || p == "c:\\" ||
			p == "C:\\Windows" || strings.HasPrefix(strings.ToLower(p), "c:\\windows") {
			continue
		}

		err := os.RemoveAll(p)
		if err != nil {
			// Log error but continue? Or return?
			// return err
			// On Windows file locking is common, best effort
		}
	}
	return nil
}

func deleteRegistryKey(fullPath string) error {
	var hive registry.Key
	var subPath string

	if strings.HasPrefix(fullPath, "HKLM\\") {
		hive = registry.LOCAL_MACHINE
		subPath = strings.TrimPrefix(fullPath, "HKLM\\")
	} else if strings.HasPrefix(fullPath, "HKCU\\") {
		hive = registry.CURRENT_USER
		subPath = strings.TrimPrefix(fullPath, "HKCU\\")
	} else {
		return nil
	}

	// Provide parent key and subkey name to DeleteKey
	// path: Software\...\Uninstall\AppGUID
	lastSlash := strings.LastIndex(subPath, `\`)
	if lastSlash == -1 {
		return nil
	}
	parentPath := subPath[:lastSlash]
	keyName := subPath[lastSlash+1:]

	k, err := registry.OpenKey(hive, parentPath, registry.WRITE)
	if err != nil {
		return err
	}
	defer k.Close()

	return registry.DeleteKey(k, keyName)
}

// RunUninstaller executes the uninstall command
func RunUninstaller(cmdString string) error {
	fmt.Printf("RunUninstaller Called with: %s\n", cmdString)
	cmd := exec.Command("cmd", "/C", cmdString)
	cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: false} // Show window so user can click next
	err := cmd.Start()
	if err != nil {
		fmt.Printf("RunUninstaller Error: %v\n", err)
		return err
	}
	fmt.Printf("RunUninstaller Started Successfully\n")
	return nil
}
