package cmd

import (
	"crypto/rand"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"time"

	"github.com/google/uuid"
)

func sha256hex() string {
	b := make([]byte, 32)
	_, _ = rand.Read(b)
	h := sha256.Sum256(b)
	return hex.EncodeToString(h[:])
}

func sha512hex() string {
	b := make([]byte, 64)
	_, _ = rand.Read(b)
	h := sha512.Sum512(b)
	return hex.EncodeToString(h[:])
}

func newUUID() string {
	return uuid.New().String()
}

func log(icon, msg string) {
	fmt.Printf("  %s %s\n", icon, msg)
}

func getCursorGlobalStorage() string {
	switch runtime.GOOS {
	case "darwin":
		home, _ := os.UserHomeDir()
		return filepath.Join(home, "Library", "Application Support", "Cursor", "User", "globalStorage")
	case "windows":
		appdata := os.Getenv("APPDATA")
		return filepath.Join(appdata, "Cursor", "User", "globalStorage")
	default:
		xdg := os.Getenv("XDG_CONFIG_HOME")
		if xdg == "" {
			home, _ := os.UserHomeDir()
			xdg = filepath.Join(home, ".config")
		}
		return filepath.Join(xdg, "Cursor", "User", "globalStorage")
	}
}

func getCursorRoot() string {
	gs := getCursorGlobalStorage()
	return filepath.Dir(filepath.Dir(gs))
}

func generateNewIDs() map[string]string {
	return map[string]string{
		"telemetry.machineId":       sha256hex(),
		"telemetry.macMachineId":    sha512hex(),
		"telemetry.devDeviceId":     newUUID(),
		"telemetry.sqmId":           "{" + fmt.Sprintf("%s", newUUID()+"") + "}",
		"storage.serviceMachineId":  newUUID(),
	}
}

func killCursor() {
	log("", "Stopping Cursor processes...")
	switch runtime.GOOS {
	case "windows":
		exec.Command("taskkill", "/F", "/IM", "Cursor.exe").Run()
	default:
		exec.Command("pkill", "-x", "Cursor").Run()
		exec.Command("pkill", "-f", "Cursor.app").Run()
	}
	log("", "Cursor stopped (or was not running)")
}

func updateStorageJSON(storagePath string, ids map[string]string) {
	if _, err := os.Stat(storagePath); os.IsNotExist(err) {
		log("", fmt.Sprintf("storage.json not found: %s", storagePath))
		return
	}

	if runtime.GOOS == "darwin" {
		exec.Command("chflags", "nouchg", storagePath).Run()
		exec.Command("chmod", "644", storagePath).Run()
	}

	data, err := os.ReadFile(storagePath)
	if err != nil {
		log("", fmt.Sprintf("storage.json read error: %v", err))
		return
	}

	var obj map[string]interface{}
	if err := json.Unmarshal(data, &obj); err != nil {
		log("", fmt.Sprintf("storage.json parse error: %v", err))
		return
	}

	for k, v := range ids {
		obj[k] = v
	}

	out, err := json.MarshalIndent(obj, "", "  ")
	if err != nil {
		log("", fmt.Sprintf("storage.json marshal error: %v", err))
		return
	}

	if err := os.WriteFile(storagePath, out, 0644); err != nil {
		log("", fmt.Sprintf("storage.json write error: %v", err))
		return
	}
	log("", "storage.json updated")
}

func updateStateVscdb(dbPath string, ids map[string]string) {
	if _, err := os.Stat(dbPath); os.IsNotExist(err) {
		log("", fmt.Sprintf("state.vscdb not found: %s", dbPath))
		return
	}

	if runtime.GOOS == "darwin" {
		exec.Command("chflags", "nouchg", dbPath).Run()
		exec.Command("chmod", "644", dbPath).Run()
	}

	if err := updateVscdbPureGo(dbPath, ids); err != nil {
		log("", fmt.Sprintf("state.vscdb error: %v", err))
	} else {
		log("", "state.vscdb updated")
	}
}

func updateMachineIDFile(machineID, cursorRoot string) {
	var candidates []string
	if runtime.GOOS == "linux" {
		candidates = []string{
			filepath.Join(cursorRoot, "machineid"),
			filepath.Join(cursorRoot, "machineId"),
		}
	} else {
		candidates = []string{filepath.Join(cursorRoot, "machineId")}
	}

	filePath := candidates[0]
	for _, c := range candidates {
		if _, err := os.Stat(c); err == nil {
			filePath = c
			break
		}
	}

	if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
		log("", fmt.Sprintf("machineId dir error: %v", err))
		return
	}

	if runtime.GOOS == "darwin" {
		if _, err := os.Stat(filePath); err == nil {
			exec.Command("chflags", "nouchg", filePath).Run()
			exec.Command("chmod", "644", filePath).Run()
		}
	}

	if err := os.WriteFile(filePath, []byte(machineID+"\n"), 0644); err != nil {
		log("", fmt.Sprintf("machineId write error: %v", err))
		return
	}
	log("", fmt.Sprintf("machineId file updated (%s)", filepath.Base(filePath)))
}

var dirsToWipe = []string{
	"Session Storage", "Local Storage", "IndexedDB", "Cache", "Code Cache",
	"GPUCache", "Service Worker", "Network", "Cookies", "Cookies-journal",
}

func deepClean(cursorRoot string) {
	log("", "Deep-cleaning session data...")
	wiped := 0
	for _, name := range dirsToWipe {
		target := filepath.Join(cursorRoot, name)
		if _, err := os.Stat(target); os.IsNotExist(err) {
			continue
		}
		info, err := os.Stat(target)
		if err != nil {
			continue
		}
		if info.IsDir() {
			if err := os.RemoveAll(target); err == nil {
				wiped++
			}
		} else {
			if err := os.Remove(target); err == nil {
				wiped++
			}
		}
	}
	log("", fmt.Sprintf("Wiped %d cache/session items", wiped))
}

func HandleResetHwid(doDeepClean, dryRun bool) error {
	fmt.Print("\nCursor HWID Reset\n\n")
	fmt.Println("  Resets all machine / telemetry IDs so Cursor sees a fresh install.")
	fmt.Print("  Cursor must be closed — it will be killed automatically.\n\n")

	globalStorage := getCursorGlobalStorage()
	cursorRoot := getCursorRoot()

	if _, err := os.Stat(globalStorage); os.IsNotExist(err) {
		fmt.Printf("Cursor config not found at:\n   %s\n", globalStorage)
		fmt.Println("   Make sure Cursor is installed and has been run at least once.")
		os.Exit(1)
	}

	if dryRun {
		fmt.Println("  [DRY RUN] Would reset IDs in:")
		fmt.Printf("    %s\n", filepath.Join(globalStorage, "storage.json"))
		fmt.Printf("    %s\n", filepath.Join(globalStorage, "state.vscdb"))
		fmt.Printf("    %s\n", filepath.Join(cursorRoot, "machineId"))
		return nil
	}

	killCursor()

	time.Sleep(800 * time.Millisecond)

	newIDs := generateNewIDs()
	log("", "Generated new IDs:")
	for k, v := range newIDs {
		fmt.Printf("       %s: %s\n", k, v)
	}
	fmt.Println()

	log("", "Updating storage.json...")
	updateStorageJSON(filepath.Join(globalStorage, "storage.json"), newIDs)

	log("", "Updating state.vscdb...")
	updateStateVscdb(filepath.Join(globalStorage, "state.vscdb"), newIDs)

	log("", "Updating machineId file...")
	updateMachineIDFile(newIDs["telemetry.machineId"], cursorRoot)

	if doDeepClean {
		fmt.Println()
		deepClean(cursorRoot)
	}

	fmt.Print("\nHWID reset complete. You can now restart Cursor.\n\n")
	return nil
}
