package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os/exec"
	"regexp"
	"runtime"
	"strings"
	"time"
)

// Software holds information about installed software
type Software struct {
	Source      string `json:"source"`
	Name        string `json:"name"`
	Version     string `json:"version"`
	InstallDate string `json:"install_date"`
}

func main() {
	var softwareList []Software

	if runtime.GOOS == "windows" {
		softwareList = append(softwareList, enumerateWindows()...)
	} else if runtime.GOOS == "linux" {
		softwareList = append(softwareList, enumerateLinux()...)
	}

	output, err := json.MarshalIndent(softwareList, "", "  ")
	if err != nil {
		log.Println("Error marshalling software list:", err)
		return
	}
	fmt.Println(string(output))
}

// Add to the enumerateWindows function
func enumerateWindows() []Software {
	var softwareList []Software

	// Winget
	softwareList = append(softwareList, enumerateWinget()...)

	// Chocolatey
	softwareList = append(softwareList, enumerateChocolatey()...)

	// Scoop
	softwareList = append(softwareList, enumerateScoop()...)

	// Common Windows Installations
	softwareList = append(softwareList, enumerateCommonWindows()...)

	// NuGet
	softwareList = append(softwareList, enumerateNuGet()...)

	// Microsoft Store
	softwareList = append(softwareList, enumerateMicrosoftStore()...)

	return softwareList
}

// Add to the enumerateLinux function
func enumerateLinux() []Software {
	var softwareList []Software

	// APT
	softwareList = append(softwareList, enumerateApt()...)

	// Snap
	softwareList = append(softwareList, enumerateSnap()...)

	// Pip
	softwareList = append(softwareList, enumeratePip()...)

	// NPM
	softwareList = append(softwareList, enumerateNpm()...)

	// Flatpak
	softwareList = append(softwareList, enumerateFlatpak()...)

	// RPM
	softwareList = append(softwareList, enumerateRpm()...)

	// Pacman
	softwareList = append(softwareList, enumeratePacman()...)

	// Homebrew
	softwareList = append(softwareList, enumerateHomebrew()...)

	// Conda
	softwareList = append(softwareList, enumerateConda()...)

	return softwareList
}

func enumerateNuGet() []Software {
	var softwareList []Software

	output, err := runCommand("nuget", "list", "-AllVersions")
	if err != nil {
		log.Println("Error enumerating NuGet packages:", err)
		return softwareList
	}

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		parts := strings.Fields(line)
		if len(parts) < 2 {
			continue
		}
		softwareList = append(softwareList, Software{
			Source:  "nuget",
			Name:    parts[0],
			Version: parts[1],
		})
	}

	return softwareList
}

func enumerateMicrosoftStore() []Software {
	var softwareList []Software

	output, err := runCommand("powershell", "Get-AppxPackage", "|", "Select-Object", "Name,PackageFullName,InstallDate")
	if err != nil {
		log.Println("Error enumerating Microsoft Store packages:", err)
		return softwareList
	}

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		parts := strings.Fields(line)
		if len(parts) < 3 {
			continue
		}
		softwareList = append(softwareList, Software{
			Source:      "microsoft store",
			Name:        parts[0],
			Version:     parts[1],
			InstallDate: parseInstallDate(parts[2]),
		})
	}

	return softwareList
}

func enumerateFlatpak() []Software {
	var softwareList []Software

	output, err := runCommand("flatpak", "list")
	if err != nil {
		log.Println("Error enumerating Flatpak packages:", err)
		return softwareList
	}

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		parts := strings.Fields(line)
		if len(parts) < 2 {
			continue
		}
		softwareList = append(softwareList, Software{
			Source:  "flatpak",
			Name:    parts[0],
			Version: parts[1],
		})
	}

	return softwareList
}

func enumerateRpm() []Software {
	var softwareList []Software

	output, err := runCommand("rpm", "-qa", "--queryformat", "%{NAME} %{VERSION}\n")
	if err != nil {
		log.Println("Error enumerating RPM packages:", err)
		return softwareList
	}

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		parts := strings.Fields(line)
		if len(parts) < 2 {
			continue
		}
		softwareList = append(softwareList, Software{
			Source:  "rpm",
			Name:    parts[0],
			Version: parts[1],
		})
	}

	return softwareList
}

func enumeratePacman() []Software {
	var softwareList []Software

	output, err := runCommand("pacman", "-Q")
	if err != nil {
		log.Println("Error enumerating Pacman packages:", err)
		return softwareList
	}

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		parts := strings.Fields(line)
		if len(parts) < 2 {
			continue
		}
		softwareList = append(softwareList, Software{
			Source:  "pacman",
			Name:    parts[0],
			Version: parts[1],
		})
	}

	return softwareList
}

func enumerateHomebrew() []Software {
	var softwareList []Software

	output, err := runCommand("brew", "list", "--versions")
	if err != nil {
		log.Println("Error enumerating Homebrew packages:", err)
		return softwareList
	}

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		parts := strings.Fields(line)
		if len(parts) < 2 {
			continue
		}
		softwareList = append(softwareList, Software{
			Source:  "homebrew",
			Name:    parts[0],
			Version: parts[1],
		})
	}

	return softwareList
}

func enumerateConda() []Software {
	var softwareList []Software

	output, err := runCommand("conda", "list", "--json")
	if err != nil {
		log.Println("Error enumerating Conda packages:", err)
		return softwareList
	}

	var condaPackages []map[string]interface{}
	err = json.Unmarshal([]byte(output), &condaPackages)
	if err != nil {
		log.Println("Error parsing Conda JSON output:", err)
		return softwareList
	}

	for _, pkg := range condaPackages {
		softwareList = append(softwareList, Software{
			Source:  "conda",
			Name:    pkg["name"].(string),
			Version: pkg["version"].(string),
		})
	}

	return softwareList
}

func runCommand(cmd string, args ...string) (string, error) {
	out, err := exec.Command(cmd, args...).Output()
	if err != nil {
		return "", err
	}
	return string(out), nil
}

func parseInstallDate(dateString string) string {
	layout := "2006-01-02"
	date, err := time.Parse(layout, dateString)
	if err != nil {
		return ""
	}
	return date.Format(layout)
}

func enumerateWinget() []Software {
	var softwareList []Software

	output, err := runCommand("winget", "list", "--source", "winget")
	if err != nil {
		log.Println("Error enumerating Winget packages:", err)
		return softwareList
	}

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		parts := regexp.MustCompile(`\s+`).Split(line, -1)
		if len(parts) < 4 {
			continue
		}
		softwareList = append(softwareList, Software{
			Source:  "winget",
			Name:    parts[0],
			Version: parts[1],
		})
	}

	return softwareList
}

func enumerateChocolatey() []Software {
	var softwareList []Software

	output, err := runCommand("choco", "list", "--local-only")
	if err != nil {
		log.Println("Error enumerating Chocolatey packages:", err)
		return softwareList
	}

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		parts := strings.Split(line, "|")
		if len(parts) < 2 {
			continue
		}
		softwareList = append(softwareList, Software{
			Source:  "chocolatey",
			Name:    parts[0],
			Version: parts[1],
		})
	}

	return softwareList
}

func enumerateScoop() []Software {
	var softwareList []Software

	output, err := runCommand("scoop", "list")
	if err != nil {
		log.Println("Error enumerating Scoop packages:", err)
		return softwareList
	}

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		if strings.HasPrefix(line, "Installed apps") {
			continue
		}
		parts := strings.Fields(line)
		if len(parts) < 2 {
			continue
		}
		softwareList = append(softwareList, Software{
			Source:  "scoop",
			Name:    parts[0],
			Version: parts[1],
		})
	}

	return softwareList
}

func enumerateCommonWindows() []Software {
	var softwareList []Software

	output, err := runCommand("powershell", "Get-WmiObject", "-Class", "Win32_Product", "|", "Select-Object", "Name,Version,InstallDate")
	if err != nil {
		log.Println("Error enumerating common Windows installations:", err)
		return softwareList
	}

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		parts := strings.Fields(line)
		if len(parts) < 3 {
			continue
		}
		softwareList = append(softwareList, Software{
			Source:      "common windows installation",
			Name:        parts[0],
			Version:     parts[1],
			InstallDate: parseInstallDate(parts[2]),
		})
	}

	return softwareList
}

func enumerateApt() []Software {
	var softwareList []Software

	output, err := runCommand("dpkg-query", "-W", "-f=${binary:Package} ${Version} ${Installed-Size}\n")
	if err != nil {
		log.Println("Error enumerating APT packages:", err)
		return softwareList
	}

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		parts := strings.Fields(line)
		if len(parts) < 2 {
			continue
		}
		softwareList = append(softwareList, Software{
			Source:  "apt",
			Name:    parts[0],
			Version: parts[1],
		})
	}

	return softwareList
}

func enumerateSnap() []Software {
	var softwareList []Software

	output, err := runCommand("snap", "list")
	if err != nil {
		log.Println("Error enumerating Snap packages:", err)
		return softwareList
	}

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		parts := strings.Fields(line)
		if len(parts) < 3 {
			continue
		}
		softwareList = append(softwareList, Software{
			Source:  "snap",
			Name:    parts[0],
			Version: parts[1],
		})
	}

	return softwareList
}

func enumeratePip() []Software {
	var softwareList []Software

	output, err := runCommand("pip", "list", "--format=json")
	if err != nil {
		log.Println("Error enumerating Pip packages:", err)
		return softwareList
	}

	var pipPackages []map[string]string
	err = json.Unmarshal([]byte(output), &pipPackages)
	if err != nil {
		log.Println("Error parsing Pip JSON output:", err)
		return softwareList
	}

	for _, pkg := range pipPackages {
		softwareList = append(softwareList, Software{
			Source:  "pip",
			Name:    pkg["name"],
			Version: pkg["version"],
		})
	}

	return softwareList
}

func enumerateNpm() []Software {
	var softwareList []Software

	output, err := runCommand("npm", "list", "-g", "--depth=0", "--json")
	if err != nil {
		log.Println("Error enumerating NPM packages:", err)
		return softwareList
	}

	var npmPackages map[string]interface{}
	err = json.Unmarshal([]byte(output), &npmPackages)
	if err != nil {
		log.Println("Error parsing NPM JSON output:", err)
		return softwareList
	}

	dependencies := npmPackages["dependencies"].(map[string]interface{})
	for name, info := range dependencies {
		infoMap := info.(map[string]interface{})
		version := infoMap["version"].(string)
		softwareList = append(softwareList, Software{
			Source:  "npm",
			Name:    name,
			Version: version,
		})
	}

	return softwareList
}
