//go:build !android && !ios && !freebsd && !js

package main

import (
	"context"
	"sync"
	"time"

	log "github.com/sirupsen/logrus"
	"github.com/wailsapp/wails/v3/pkg/application"
	"github.com/wailsapp/wails/v3/pkg/services/notifications"

	"github.com/netbirdio/netbird/client/ui/services"
	"github.com/netbirdio/netbird/client/ui/updater"
	"github.com/netbirdio/netbird/version"
)

// trayUpdater owns the tray UI that reacts to auto-update. Composed inside Tray.
type trayUpdater struct {
	app          *application.App
	window       *application.WebviewWindow
	update       *services.Update
	notifier     *notifications.NotificationService
	loc          *Localizer
	onIconChange func()
	// onMenuChange drives a full tray relayout: the update row lives in the
	// About submenu, which KDE/Plasma caches on first open and never re-fetches
	// on a plain SetLabel/SetHidden — only a relayout (fresh submenu ids) repaints.
	onMenuChange func()

	mu                 sync.Mutex
	item               *application.MenuItem
	state              updater.State
	notifiedVersion    string
	progressWindowOpen bool
}

func newTrayUpdater(app *application.App, window *application.WebviewWindow, update *services.Update, notifier *notifications.NotificationService, loc *Localizer, onIconChange func(), onMenuChange func()) *trayUpdater {
	u := &trayUpdater{
		app:          app,
		window:       window,
		update:       update,
		notifier:     notifier,
		loc:          loc,
		onIconChange: onIconChange,
		onMenuChange: onMenuChange,
	}
	app.Event.On(updater.EventStateChanged, u.onStateEvent)
	// Seed from cached state to cover an event that fired before wiring completed.
	u.state = update.GetState()
	return u
}

// attach (re)binds the menu item on each Tray.buildMenu run. The caller owns the
// item's OnClick handler.
func (u *trayUpdater) attach(item *application.MenuItem) {
	u.mu.Lock()
	u.item = item
	state := u.state
	u.mu.Unlock()
	u.refreshMenuItem(state)
}

// hasUpdate reports whether the tray should paint the "update available" icon.
func (u *trayUpdater) hasUpdate() bool {
	u.mu.Lock()
	defer u.mu.Unlock()
	return u.state.Available
}

// applyLanguage re-renders the menu item label after a locale switch.
func (u *trayUpdater) applyLanguage() {
	u.mu.Lock()
	state := u.state
	u.mu.Unlock()
	u.refreshMenuItem(state)
}

// handleClick opens the installer download link when not Enforced, otherwise
// shows the progress page and asks the daemon to start the installer.
func (u *trayUpdater) handleClick() {
	u.mu.Lock()
	state := u.state
	u.mu.Unlock()

	if !state.Enforced {
		_ = u.app.Browser.OpenURL(version.DownloadUrl())
		return
	}

	u.openProgressWindow(state.Version)

	go func() {
		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		defer cancel()
		if _, err := u.update.Trigger(ctx); err != nil {
			log.Errorf("trigger update: %v", err)
		}
	}()
}

func (u *trayUpdater) onStateEvent(ev *application.CustomEvent) {
	st, ok := ev.Data.(updater.State)
	if !ok {
		log.Warnf("update state event payload not UpdateState: %T", ev.Data)
		return
	}
	u.applyState(st)
}

// applyState diffs st against the cached state and drives the resulting side
// effects: icon repaint, menu refresh, new-version notification, progress window.
func (u *trayUpdater) applyState(st updater.State) {
	u.mu.Lock()
	prev := u.state
	u.state = st

	sendNotify := st.Available && st.Version != "" && st.Version != u.notifiedVersion
	if sendNotify {
		u.notifiedVersion = st.Version
	}

	showWindow := st.Installing && !u.progressWindowOpen
	if showWindow {
		u.progressWindowOpen = true
	} else if !st.Installing {
		u.progressWindowOpen = false
	}
	u.mu.Unlock()

	// Full relayout rather than in-place: KDE layout-caches the About submenu, so
	// a direct SetLabel/SetHidden wouldn't paint. Fall back if no hook was wired.
	if u.onMenuChange != nil {
		u.onMenuChange()
	} else {
		u.refreshMenuItem(st)
	}
	if prev.Available != st.Available && u.onIconChange != nil {
		u.onIconChange()
	}
	if sendNotify {
		u.sendUpdateNotification(st)
	}
	if showWindow {
		u.openProgressWindow(st.Version)
	}
}

func (u *trayUpdater) refreshMenuItem(st updater.State) {
	u.mu.Lock()
	item := u.item
	u.mu.Unlock()
	if item == nil {
		return
	}

	if !st.Available {
		item.SetHidden(true)
		return
	}
	if st.Enforced {
		item.SetLabel(u.loc.T("tray.menu.installVersion", "version", st.Version))
	} else {
		item.SetLabel(u.loc.T("tray.menu.downloadLatest"))
	}
	item.SetHidden(false)
}

func (u *trayUpdater) sendUpdateNotification(st updater.State) {
	if u.notifier == nil {
		return
	}
	body := u.loc.T("notify.update.body", "version", st.Version)
	if st.Enforced {
		body += u.loc.T("notify.update.enforcedSuffix")
	}
	_ = safeSendNotification(u.notifier.SendNotification, "update", notifications.NotificationOptions{
		ID:    notifyIDUpdatePrefix + st.Version,
		Title: u.loc.T("notify.update.title"),
		Body:  body,
	})
}

// openProgressWindow points the main window at the /update progress page and
// brings it forward.
func (u *trayUpdater) openProgressWindow(version string) {
	if u.window == nil {
		return
	}
	url := "/#/update"
	if version != "" {
		url += "?version=" + version
	}
	u.window.SetURL(url)
	u.window.Show()
	u.window.Focus()
}
