package apps

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
)

type AppInfo struct {
	Name     string `json:"name"`
	Path     string `json:"path"`
	BundleID string `json:"bundleID"`
	Size     int64  `json:"size"`
	Icon     string `json:"icon,omitempty"` // Base64 or path? For now just path to .app (frontend can get icon)
}

type AssociatedFile struct {
	Path string `json:"path"`
	Type string `json:"type"` // "cache", "config", "log", "data"
	Size int64  `json:"size"`
}

type AppDetails struct {
	AppInfo
	Associated []AssociatedFile `json:"associated"`
	TotalSize  int64            `json:"totalSize"`
}

// ScanApps returns a list of installed applications
func ScanApps() ([]AppInfo, error) {
	// Scan /Applications and ~/Applications
	home, _ := os.UserHomeDir()
	dirs := []string{
		"/Applications",
		filepath.Join(home, "Applications"),
	}

	var apps []AppInfo
	var mu sync.Mutex
	var wg sync.WaitGroup

	for _, dir := range dirs {
		entries, err := os.ReadDir(dir)
		if err != nil {
			continue
		}

		for _, entry := range entries {
			if strings.HasSuffix(entry.Name(), ".app") {
				path := filepath.Join(dir, entry.Name())
				wg.Add(1)
				go func(p, name string) {
					defer wg.Done()
					// Get Bundle ID
					bid := getBundleID(p)
					if bid == "" {
						return // Skip if no bundle ID (system util or broken)
					}

					// Get Size (fast estimate)
					// using du -s -k
					size := getDirSize(p)

					mu.Lock()
					apps = append(apps, AppInfo{
						Name:     strings.TrimSuffix(name, ".app"),
						Path:     p,
						BundleID: bid,
						Size:     size,
					})
					mu.Unlock()
				}(path, entry.Name())
			}
		}
	}
	wg.Wait()
	return apps, nil
}

// GetAppDetails finds all associated files for a given app path
func GetAppDetails(appPath string) (*AppDetails, error) {
	bid := getBundleID(appPath)
	if bid == "" {
		return nil, fmt.Errorf("could not determine bundle ID")
	}

	appSize := getDirSize(appPath)
	details := &AppDetails{
		AppInfo: AppInfo{
			Name:     filepath.Base(appPath), // simplified
			Path:     appPath,
			BundleID: bid,
			Size:     appSize,
		},
		TotalSize: appSize,
	}

	home, _ := os.UserHomeDir()
	library := filepath.Join(home, "Library")

	// Common locations to search for Bundle ID
	// Name-based search fallback is risky, staying strict to Bundle ID for now
	locations := map[string]string{
		"Application Support":     filepath.Join(library, "Application Support"),
		"Caches":                  filepath.Join(library, "Caches"),
		"Preferences":             filepath.Join(library, "Preferences"),
		"Saved Application State": filepath.Join(library, "Saved Application State"),
		"Logs":                    filepath.Join(library, "Logs"),
		"Cookies":                 filepath.Join(library, "Cookies"),
		"Containers":              filepath.Join(library, "Containers"), // Sandboxed data
	}

	for locName, locPath := range locations {
		// 1. Direct match: path/BundleID
		target := filepath.Join(locPath, bid)
		if exists(target) {
			size := getDirSize(target)
			details.Associated = append(details.Associated, AssociatedFile{
				Path: target,
				Type: getType(locName),
				Size: size,
			})
			details.TotalSize += size
		}

		// 2. Preferences often use plist extension: path/BundleID.plist
		if locName == "Preferences" {
			plistPath := filepath.Join(locPath, bid+".plist")
			if exists(plistPath) {
				size := getFileSize(plistPath)
				details.Associated = append(details.Associated, AssociatedFile{
					Path: plistPath,
					Type: "config",
					Size: size,
				})
				details.TotalSize += size
			}
		}
	}

	return details, nil
}

// DeleteFiles removes the requested paths
func DeleteFiles(paths []string) error {
	for _, p := range paths {
		// Basic safety check: don't delete root or critical system paths
		// Real implementation needs robust safeguards
		if p == "/" || p == "/Applications" || p == "/System" || p == "/Library" {
			continue // Skip dangerous paths
		}
		if err := os.RemoveAll(p); err != nil {
			return err
		}
	}
	return nil
}

// Helpers

func getBundleID(path string) string {
	// mdls -name kMDItemCFBundleIdentifier -r /path/to/app
	cmd := exec.Command("mdls", "-name", "kMDItemCFBundleIdentifier", "-r", path)
	out, err := cmd.Output()
	if err != nil {
		return ""
	}
	res := strings.TrimSpace(string(out))
	if res == "(null)" {
		return ""
	}
	return res
}

func getDirSize(path string) int64 {
	cmd := exec.Command("du", "-s", "-k", path)
	out, err := cmd.Output()
	if err != nil {
		return 0
	}
	parts := strings.Fields(string(out))
	if len(parts) > 0 {
		var s int64
		fmt.Sscanf(parts[0], "%d", &s)
		return s * 1024
	}
	return 0
}

func getFileSize(path string) int64 {
	info, err := os.Stat(path)
	if err != nil {
		return 0
	}
	return info.Size()
}

func exists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

func getType(locName string) string {
	switch locName {
	case "Caches":
		return "cache"
	case "Preferences", "Cookies":
		return "config"
	case "Logs":
		return "log"
	default:
		return "data"
	}
}
