//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 silently checks for and applies updates whenever
// drover.exe starts as a GUI app (no CLI subcommand). Chrome-style: no
// prompt, no progress bar, no questions — if an update is available we
// download, verify, apply, and re-launch. The user just sees the new
// version's window appear instead of the old one.
//
// Network failures, server outages, slow downloads, and "no updates
// available" are silent fall-throughs — startup must never block on them.
//
// Two split contexts:
//   - check  (8s)  — quick HEAD-equivalent against the releases API
//   - apply (60s)  — actual download + sha256 + atomic replace
func autoUpdateOnStartup() {
	src := updater.NewForgejoSource("git.okcu.io", "root", "drover-go", "windows-amd64.exe")

	checkCtx, cancelCheck := context.WithTimeout(context.Background(), 8*time.Second)
	rel, hasUpdate, err := updater.CheckForUpdate(checkCtx, src, Version)
	cancelCheck()
	if err != nil || !hasUpdate {
		return
	}

	applyCtx, cancelApply := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancelApply()
	if err := updater.ApplyUpdate(applyCtx, rel, nil); err != nil {
		// Apply failed — surface this one (sha mismatch, write error,
		// disk full are not silent-fail cases). The user can keep using
		// the current version after dismissing the dialog.
		errorDialog(fmt.Sprintf("Update to %s failed: %v\n\nContinuing on current version.", rel.TagName, 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 takes over.
	os.Exit(0)
}

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()
}
