//go:build windows

package main

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"time"
	"unsafe"

	"git.okcu.io/root/drover-go/internal/updater"
	"golang.org/x/sys/windows"
)

// autoUpdateOnStartup runs a non-interactive update check whenever drover.exe
// starts as a GUI app (no CLI subcommand). If an update is available, a
// Yes/No message box prompts the user; on Yes we download, verify, apply
// the update, then re-launch the binary so the new version is what they see.
//
// Network failures, server outages, and "no updates available" are silent
// fall-throughs — startup must never block on them.
func autoUpdateOnStartup() {
	// Tight timeout — startup, not a long-running task.
	ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
	defer cancel()

	src := updater.NewForgejoSource("git.okcu.io", "root", "drover-go", "windows-amd64.exe")
	rel, hasUpdate, err := updater.CheckForUpdate(ctx, src, Version)
	if err != nil || !hasUpdate {
		// Silent: offline, slow network, or already up to date — none of
		// these should interrupt the user.
		return
	}

	if !confirmUpdateDialog(rel) {
		return
	}

	if err := updater.ApplyUpdate(ctx, rel, nil); err != nil {
		errorDialog(fmt.Sprintf("Update failed: %v", err))
		return
	}

	if err := relaunchSelf(); err != nil {
		errorDialog(fmt.Sprintf("Update applied but re-launch failed: %v\n\nPlease restart drover.exe manually.", err))
		return
	}

	// Successfully spawned the new version — exit cleanly so it can take over.
	os.Exit(0)
}

// confirmUpdateDialog asks the user whether to apply the available update.
// Returns true on Yes (IDYES = 6).
func confirmUpdateDialog(rel *updater.Release) bool {
	user32 := windows.NewLazySystemDLL("user32.dll")
	messageBox := user32.NewProc("MessageBoxW")

	body := fmt.Sprintf(
		"A new version is available.\n\n"+
			"Current:  v%s\n"+
			"Latest:   %s\n\n"+
			"Install it now? Drover-Go will restart automatically.",
		Version, rel.TagName,
	)
	title := "Drover-Go — Update available"

	bodyW, _ := windows.UTF16PtrFromString(body)
	titleW, _ := windows.UTF16PtrFromString(title)

	// MB_YESNO | MB_ICONQUESTION | MB_SETFOREGROUND | MB_TOPMOST | MB_DEFBUTTON1
	const flags = 0x00000004 | 0x00000020 | 0x00010000 | 0x00040000 | 0x00000000

	r, _, _ := messageBox.Call(0, uintptr(unsafe.Pointer(bodyW)), uintptr(unsafe.Pointer(titleW)), flags)
	const IDYES = 6
	return r == IDYES
}

func errorDialog(msg string) {
	user32 := windows.NewLazySystemDLL("user32.dll")
	messageBox := user32.NewProc("MessageBoxW")

	bodyW, _ := windows.UTF16PtrFromString(msg)
	titleW, _ := windows.UTF16PtrFromString("Drover-Go — Error")

	// MB_OK | MB_ICONERROR | MB_TOPMOST
	const flags = 0x00000000 | 0x00000010 | 0x00040000

	messageBox.Call(0, uintptr(unsafe.Pointer(bodyW)), uintptr(unsafe.Pointer(titleW)), flags)
}

// relaunchSelf starts a fresh copy of the (now-updated) executable in the
// background and returns. The caller is expected to os.Exit(0) immediately
// after — the OS handles the brief overlap fine, and the new process inherits
// nothing from us beyond the working directory and arguments.
func relaunchSelf() error {
	exe, err := os.Executable()
	if err != nil {
		return fmt.Errorf("locate self: %w", err)
	}
	cmd := exec.Command(exe, os.Args[1:]...)
	cmd.Stdin, cmd.Stdout, cmd.Stderr = nil, nil, nil
	return cmd.Start()
}
