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

package main

import (
	"context"
	"fmt"
	"sort"

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

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

// loadConfig seeds the in-process notifications gate from the daemon's
// stored config and caches the active-profile identity for any future
// SetConfig calls. Called once at startup from a goroutine so a slow or
// unreachable daemon does not block menu construction.
//
// The Settings page in the main window is the source of truth for every
// other knob (SSH, auto-connect, Rosenpass, lazy connections, block-inbound,
// notifications); we only mirror the notifications flag because the tray
// itself uses it to gate OS toasts in onSystemEvent.
func (t *Tray) loadConfig() {
	ctx := context.Background()

	active, err := t.svc.Profiles.GetActive(ctx)
	if err != nil {
		log.Debugf("get active profile: %v", err)
		return
	}
	cfg, err := t.svc.Settings.GetConfig(ctx, services.ConfigParams(active))
	if err != nil {
		log.Debugf("get config: %v", err)
		return
	}

	t.profileMu.Lock()
	t.activeProfile = active.ProfileName
	t.activeUsername = active.Username
	t.notificationsEnabled = !cfg.DisableNotifications
	t.profileMu.Unlock()
}

// loadProfiles fetches the profile list from the daemon, caches it under
// profilesMu, and drives a full tray relayout (relayoutMenu) so the Profiles
// submenu repaints. Called on ApplicationStarted, after a successful
// switchProfile, and from applyStatus whenever the daemon's status text
// changes — the last case catches profile flips driven by another channel
// (CLI "netbird profile select", autoconnect picking the persisted profile
// after the UI's first ListProfiles, etc.) since the daemon does not emit a
// dedicated active-profile event. The relayout (rather than a Clear()+Add()
// into the live submenu) is what makes KDE/Plasma actually repaint and keep
// the click→id mapping live — see relayoutMenu's doc comment.
func (t *Tray) loadProfiles() {
	if t.profileSubmenu == nil {
		return
	}
	t.profileLoadMu.Lock()
	defer t.profileLoadMu.Unlock()
	ctx := context.Background()

	username, err := t.svc.Profiles.Username()
	if err != nil {
		log.Debugf("get current user: %v", err)
		return
	}
	profiles, err := t.svc.Profiles.List(ctx, username)
	if err != nil {
		log.Debugf("list profiles: %v", err)
		return
	}

	t.profilesMu.Lock()
	t.profiles = profiles
	t.profilesUser = username
	t.profilesMu.Unlock()

	t.relayoutMenu()
}

// fillProfileSubmenu paints the cached profile rows into the (freshly built)
// Profiles submenu and updates the parent label + email row. Pure UI: it
// never fetches and never calls SetMenu — relayoutMenu owns the single
// SetMenu that pushes the whole tree. Reads the rows captured by loadProfiles
// under profilesMu.
func (t *Tray) fillProfileSubmenu() {
	if t.profileSubmenu == nil {
		return
	}
	t.profilesMu.Lock()
	profiles := append([]services.Profile(nil), t.profiles...)
	username := t.profilesUser
	t.profilesMu.Unlock()

	sort.Slice(profiles, func(i, j int) bool { return profiles[i].Name < profiles[j].Name })

	t.profileSubmenu.Clear()
	var activeName, activeEmail string
	for _, p := range profiles {
		name := p.Name
		active := p.IsActive
		// Use Add instead of AddCheckbox: Wails auto-toggles a checkbox's
		// checked state on click (before the OnClick handler fires), so with
		// AddCheckbox both the old and the new profile would briefly show as
		// checked while the switchProfile goroutine is running. A plain item
		// with a "✓ " prefix avoids the race entirely.
		label := name
		if active {
			label = "✓ " + name
		}
		item := t.profileSubmenu.Add(label)
		item.OnClick(func(*application.Context) {
			log.Infof("tray profile click: profile=%q wasActive=%v", name, active)
			if active {
				return
			}
			t.switchProfile(name)
		})
		if active {
			activeName = name
			activeEmail = p.Email
		}
	}
	t.profileSubmenu.AddSeparator()
	t.profileSubmenu.Add(t.loc.T("tray.menu.manageProfiles")).OnClick(func(*application.Context) {
		t.svc.WindowManager.OpenSettings("profiles")
	})
	log.Infof("tray fillProfileSubmenu: %d profile(s) for user %q, active=%q", len(profiles), username, activeName)
	if t.profileSubmenuItem != nil && activeName != "" {
		t.profileSubmenuItem.SetLabel(activeName)
	}
	if t.profileEmailItem != nil {
		if activeEmail != "" {
			t.profileEmailItem.SetLabel(fmt.Sprintf("(%s)", activeEmail))
			t.profileEmailItem.SetHidden(false)
		} else {
			t.profileEmailItem.SetHidden(true)
		}
	}
}

// switchProfile cancels any in-flight profile switch, then starts a new one.
// Cancelling the previous context aborts its in-flight gRPC calls (Down/Up)
// so rapid clicks always converge to the last selected profile.
//
// The optimistic Connecting paint (and suppression of the transient
// Idle/stale Connected daemon events that follow Down) lives in
// services/daemon_feed.go — ProfileSwitcher calls DaemonFeed.BeginProfileSwitch
// when the previous status was Connected/Connecting, which emits a
// synthetic Connecting status to the event bus and starts filtering
// the daemon stream. That way both this tray and the React Status
// page see the same optimistic state without duplicating policy.
func (t *Tray) switchProfile(name string) {
	t.profileMu.Lock()
	if t.switchCancel != nil {
		t.switchCancel()
	}
	ctx, cancel := context.WithCancel(context.Background())
	t.switchCancel = cancel
	t.profileMu.Unlock()

	go func() {
		username, err := t.svc.Profiles.Username()
		if err != nil {
			log.Errorf("tray switchProfile: get current user: %v", err)
			return
		}
		if err := t.svc.ProfileSwitcher.SwitchActive(ctx, services.ProfileRef{
			ProfileName: name,
			Username:    username,
		}); err != nil {
			if ctx.Err() != nil {
				return
			}
			log.Errorf("tray switchProfile: %v", err)
			t.notifyError(t.loc.T("notify.error.switchProfile", "profile", name))
			return
		}
		t.loadProfiles()
	}()
}
