//go:build windows

package scanner

import (
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"sync"
	"syscall"
	"unsafe"
	// Added missing import
)

// GetDiskUsage using GetDiskFreeSpaceExW
// GetDiskUsage returns usage for all fixed drives
func GetDiskUsage() ([]*DiskUsage, error) {
	kernel32 := syscall.NewLazyDLL("kernel32.dll")
	getDiskFreeSpaceEx := kernel32.NewProc("GetDiskFreeSpaceExW")
	getLogicalDrives := kernel32.NewProc("GetLogicalDrives")

	var usages []*DiskUsage

	// Get logical drives bitmask
	ret, _, _ := getLogicalDrives.Call()
	if ret == 0 {
		return nil, fmt.Errorf("GetLogicalDrives failed")
	}
	drivesBitmask := uint32(ret)

	toGB := func(bytes int64) string {
		gb := float64(bytes) / 1024 / 1024 / 1024
		return fmt.Sprintf("%.2f", gb)
	}

	for i := 0; i < 26; i++ {
		if drivesBitmask&(1<<uint(i)) != 0 {
			driveLetter := string(rune('A' + i))
			root := driveLetter + ":\\"

			// Check drive type? strictly speaking GetDiskFreeSpaceEx works on network too.
			// Ideally check GetDriveType to avoid floppy/cd, but usually no biggie if we just check free space.

			var freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes int64
			pathPtr, _ := syscall.UTF16PtrFromString(root)

			r, _, _ := getDiskFreeSpaceEx.Call(
				uintptr(unsafe.Pointer(pathPtr)),
				uintptr(unsafe.Pointer(&freeBytesAvailable)),
				uintptr(unsafe.Pointer(&totalNumberOfBytes)),
				uintptr(unsafe.Pointer(&totalNumberOfFreeBytes)),
			)

			if r != 0 && totalNumberOfBytes > 0 {
				usedBytes := totalNumberOfBytes - totalNumberOfFreeBytes
				usages = append(usages, &DiskUsage{
					Name:    fmt.Sprintf("Local Disk (%s:)", driveLetter),
					TotalGB: toGB(totalNumberOfBytes),
					UsedGB:  toGB(usedBytes),
					FreeGB:  toGB(totalNumberOfFreeBytes),
				})
			}
		}
	}

	return usages, nil
}

// GetDirectorySize walks the directory to calculate size (Windows doesn't have `du`)
func GetDirectorySize(path string) int64 {
	var size int64
	filepath.WalkDir(path, func(_ string, d os.DirEntry, err error) error {
		if err != nil {
			return nil
		}
		if !d.IsDir() {
			info, err := d.Info()
			if err == nil {
				size += info.Size()
			}
		}
		return nil
	})
	return size
}

// FindHeavyFolders finds large directories
func FindHeavyFolders(root string) ([]ScanResult, error) {
	// Basic implementation: Walk max 2 levels deep and calculate sizes
	var results []ScanResult

	// depth 0 = root
	// depth 1 = children of root
	// depth 2 = children of children

	entries, err := os.ReadDir(root)
	if err != nil {
		return nil, err
	}

	var wg sync.WaitGroup
	var mu sync.Mutex

	for _, entry := range entries {
		if entry.IsDir() {
			path := filepath.Join(root, entry.Name())
			wg.Add(1)
			go func(p string) {
				defer wg.Done()
				s := GetDirectorySize(p)
				mu.Lock()
				results = append(results, ScanResult{
					Path:        p,
					Size:        s,
					IsDirectory: true,
				})
				mu.Unlock()
			}(path)
		}
	}
	wg.Wait()

	// Sort by size desc
	sort.Slice(results, func(i, j int) bool {
		return results[i].Size > results[j].Size
	})

	if len(results) > 50 {
		return results[:50], nil
	}
	return results, nil
}

func ScanUserDocuments() ([]ScanResult, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return nil, err
	}

	targets := []string{
		filepath.Join(home, "Documents"),
		filepath.Join(home, "Downloads"),
		filepath.Join(home, "Desktop"),
	}

	var allResults []ScanResult
	for _, t := range targets {
		res, _ := FindLargeFiles(t, 10*1024*1024) // 10MB
		allResults = append(allResults, res...)
	}

	sort.Slice(allResults, func(i, j int) bool {
		return allResults[i].Size > allResults[j].Size
	})

	if len(allResults) > 50 {
		return allResults[:50], nil
	}
	return allResults, nil
}

func ScanSystemData() ([]ScanResult, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return nil, err
	}

	// Windows System/Temp locations
	// %Temp%, Prefetch (admin only, careful), AppData/Local/Temp
	targets := []string{
		filepath.Join(home, "AppData", "Local", "Temp"),
		os.Getenv("TEMP"),
		// "C:\\Windows\\Temp", // Requires Admin, maybe skip for now or handle error
	}

	var allResults []ScanResult
	for _, t := range targets {
		if t == "" {
			continue
		}
		res, _ := FindLargeFiles(t, 10*1024*1024)
		allResults = append(allResults, res...)
	}

	sort.Slice(allResults, func(i, j int) bool {
		return allResults[i].Size > allResults[j].Size
	})

	if len(allResults) > 50 {
		return allResults[:50], nil
	}
	return allResults, nil
}

func GetCategorySizes() (*CategorySizes, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return nil, err
	}

	docPath := filepath.Join(home, "Documents")
	downPath := filepath.Join(home, "Downloads")
	deskPath := filepath.Join(home, "Desktop")
	musicPath := filepath.Join(home, "Music")
	moviesPath := filepath.Join(home, "Videos") // Windows uses Videos
	photos := filepath.Join(home, "Pictures")

	// AppData is roughly Library
	localAppData := filepath.Join(home, "AppData", "Local")
	temp := filepath.Join(localAppData, "Temp")

	// Parallel fetch
	type result struct {
		name string
		size int64
	}
	c := make(chan result)
	// Checks: docs, down, desk, music, movies, temp, photos, archives, vms, games, ai, docker, cache
	totalChecks := 13

	check := func(name, p string) {
		c <- result{name, GetDirectorySize(p)}
	}

	go check("docs", docPath)
	go check("down", downPath)
	go check("desk", deskPath)
	go check("music", musicPath)
	go check("movies", moviesPath)
	// Temp is part of Cache now, but let's keep it separate or sum it up
	// System/Temp logic from before:
	go check("temp", temp)
	go check("photos", photos)

	// Scan specific common folders for Archives and VMs
	go func() {
		// Archives: Zip, Rar, 7z in Downloads and Documents
		size := ScanExtensions(downPath, []string{".zip", ".rar", ".7z", ".tar", ".gz", ".xz"})
		size += ScanExtensions(docPath, []string{".zip", ".rar", ".7z", ".tar", ".gz", ".xz"})
		c <- result{"archives", size}
	}()

	go func() {
		// VMs / Disk Images: ISO, VHDX, VMDK in Downloads and Documents
		size := ScanExtensions(downPath, []string{".iso", ".vdi", ".vmdk", ".qcow2", ".vhdx", ".img", ".dsk"})
		size += ScanExtensions(docPath, []string{".iso", ".vdi", ".vmdk", ".qcow2", ".vhdx", ".img", ".dsk"})
		c <- result{"vms", size}
	}()

	// Games
	go func() {
		var size int64
		// Common Game Paths
		paths := []string{
			`C:\Program Files (x86)\Steam\steamapps\common`,
			`C:\Program Files\Epic Games`,
			`C:\Program Files (x86)\Ubisoft\Ubisoft Game Launcher\games`,
			`C:\Program Files\EA Games`,
			filepath.Join(home, "AppData", "Roaming", ".minecraft"),
		}
		for _, p := range paths {
			size += GetDirectorySize(p)
		}
		c <- result{"games", size}
	}()

	// AI
	go func() {
		var size int64
		// 1. Common Installation Paths
		paths := []string{
			`C:\ComfyUI`,
			`C:\ai\ComfyUI`,
			filepath.Join(home, "ComfyUI"),
			filepath.Join(home, "stable-diffusion-webui"),
			filepath.Join(home, "webui"),
			// Common Model Caches
			filepath.Join(home, ".cache", "huggingface"),
			filepath.Join(home, ".ollama", "models"),
			filepath.Join(home, ".lmstudio", "models"),
		}
		for _, p := range paths {
			size += GetDirectorySize(p)
		}

		// 2. Loose Model Files (Deep Scan)
		// Look for .safetensors, .ckpt, .gguf, .pt, .pth, .bin, .onnx in Downloads and Documents
		aiExtensions := []string{".safetensors", ".ckpt", ".gguf", ".pt", ".pth", ".bin", ".onnx"}
		size += ScanExtensions(downPath, aiExtensions)
		size += ScanExtensions(docPath, aiExtensions)

		c <- result{"ai", size}
	}()

	// Docker
	go func() {
		var size int64
		// Docker Desktop Default WSL Data
		dockerPath := filepath.Join(localAppData, "Docker", "wsl", "data", "ext4.vhdx")
		info, err := os.Stat(dockerPath)
		if err == nil {
			size = info.Size()
		}
		c <- result{"docker", size}
	}()

	// Cache (Browser + System Temp)
	go func() {
		var size int64
		// System Temp
		size += GetDirectorySize(os.Getenv("TEMP"))

		// Chrome Cache
		size += GetDirectorySize(filepath.Join(localAppData, "Google", "Chrome", "User Data", "Default", "Cache"))
		// Edge Cache
		size += GetDirectorySize(filepath.Join(localAppData, "Microsoft", "Edge", "User Data", "Default", "Cache"))
		// Brave Cache
		size += GetDirectorySize(filepath.Join(localAppData, "BraveSoftware", "Brave-Browser", "User Data", "Default", "Cache"))
		// Opera Cache
		size += GetDirectorySize(filepath.Join(localAppData, "Opera Software", "Opera Stable", "Cache"))
		// Firefox Cache
		size += GetDirectorySize(filepath.Join(localAppData, "Mozilla", "Firefox", "Profiles")) // Scan all profiles for cache? Usually in Local/Mozilla/Firefox/Profiles/<profile>/cache2

		// Firefox requires walking profiles in LocalAppData
		mozPath := filepath.Join(localAppData, "Mozilla", "Firefox", "Profiles")
		entries, _ := os.ReadDir(mozPath)
		for _, e := range entries {
			if e.IsDir() {
				size += GetDirectorySize(filepath.Join(mozPath, e.Name(), "cache2"))
			}
		}

		c <- result{"cache", size}
	}()

	sizes := &CategorySizes{}

	for i := 0; i < totalChecks; i++ {
		res := <-c
		switch res.name {
		case "docs":
			sizes.Documents = res.size
		case "down":
			sizes.Downloads = res.size
		case "desk":
			sizes.Desktop = res.size
		case "music":
			sizes.Music = res.size
		case "movies":
			sizes.Movies = res.size
		case "temp":
			// Keeping legacy System field for now, maybe map to part of Cache or System logs?
			sizes.System = res.size
		case "photos":
			sizes.Photos = res.size
		case "archives":
			sizes.Archives = res.size
		case "vms":
			sizes.VirtualMachines = res.size
		case "games":
			sizes.Games = res.size
		case "ai":
			sizes.AI = res.size
		case "docker":
			sizes.Docker = res.size
		case "cache":
			sizes.Cache = res.size
		}
	}

	return sizes, nil
}

// ScanExtensions walks a directory and sums up size of files with matching extensions
func ScanExtensions(root string, exts []string) int64 {
	var total int64
	extMap := make(map[string]bool)
	for _, e := range exts {
		extMap[strings.ToLower(e)] = true
	}

	filepath.WalkDir(root, func(_ string, d os.DirEntry, err error) error {
		if err != nil {
			return nil
		}
		if !d.IsDir() {
			ext := strings.ToLower(filepath.Ext(d.Name()))
			if extMap[ext] {
				info, err := d.Info()
				if err == nil {
					total += info.Size()
				}
			}
		}
		return nil
	})
	return total
}

func GetCleaningEstimates() (*CleaningEstimates, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return nil, err
	}

	// Flash Clean: Temp files
	temp := filepath.Join(home, "AppData", "Local", "Temp")

	// Deep Clean: Downloads
	downloads := filepath.Join(home, "Downloads")

	type result struct {
		name string
		size int64
	}
	c := make(chan result)

	go func() { c <- result{"temp", GetDirectorySize(temp)} }()
	go func() { c <- result{"downloads", GetDirectorySize(downloads)} }()

	estimates := &CleaningEstimates{}

	for i := 0; i < 2; i++ {
		res := <-c
		switch res.name {
		case "temp":
			estimates.FlashEst = res.size
		case "downloads":
			estimates.DeepEst = res.size
		}
	}
	return estimates, nil
}
