package status

import (
	"errors"
	"fmt"
	"sync"
	"sync/atomic"

	"github.com/safing/portmaster/base/notifications"
	"github.com/safing/portmaster/base/runtime"
	"github.com/safing/portmaster/base/utils/debug"
	"github.com/safing/portmaster/service/mgr"
	"github.com/safing/portmaster/service/netenv"
)

// Status Module manages status information.
type Status struct {
	mgr      *mgr.Manager
	instance instance

	publishUpdate runtime.PushFunc
	triggerUpdate chan struct{}

	states     map[string]mgr.StateUpdate
	statesLock sync.Mutex

	notifications     map[string]map[string]*notifications.Notification
	notificationsLock sync.Mutex
}

// Manager returns the module manager.
func (s *Status) Manager() *mgr.Manager {
	return s.mgr
}

// Start starts the module.
func (s *Status) Start() error {
	s.mgr.Go("status publisher", s.statusPublisher)

	s.instance.NetEnv().EventOnlineStatusChange.AddCallback("update online status in system status",
		func(_ *mgr.WorkerCtx, _ netenv.OnlineStatus) (bool, error) {
			s.triggerPublishStatus()
			return false, nil
		},
	)

	// Make an initial status query.
	s.statesLock.Lock()
	defer s.statesLock.Unlock()
	// Add status callback within the lock so we can force the right order.
	s.instance.AddStatesCallback("status update", s.handleModuleStatusUpdate)
	// Get initial states.
	for _, stateUpdate := range s.instance.GetStates() {
		s.states[stateUpdate.Module] = stateUpdate
		s.deriveNotificationsFromStateUpdate(stateUpdate)
	}

	return nil
}

// Stop stops the module.
func (s *Status) Stop() error {
	return nil
}

func (s *Status) prep() error {
	// register status provider as soon as possible
	if err := s.setupRuntimeProvider(); err != nil {
		return err
	}
	return nil
}

// AddToDebugInfo adds the system status to the given debug.Info.
func AddToDebugInfo(di *debug.Info) {
	di.AddSection(
		fmt.Sprintf("Status: %s", netenv.GetOnlineStatus()),
		debug.UseCodeSection|debug.AddContentLineBreaks,
		fmt.Sprintf("OnlineStatus:          %s", netenv.GetOnlineStatus()),
		"CaptivePortal:         "+netenv.GetCaptivePortal().URL,
	)
}

var (
	module     *Status
	shimLoaded atomic.Bool
)

// New returns a new status module.
func New(instance instance) (*Status, error) {
	if !shimLoaded.CompareAndSwap(false, true) {
		return nil, errors.New("only one instance allowed")
	}
	m := mgr.New("Status")
	module = &Status{
		mgr:           m,
		instance:      instance,
		triggerUpdate: make(chan struct{}, 1),
		states:        make(map[string]mgr.StateUpdate),
		notifications: make(map[string]map[string]*notifications.Notification),
	}

	if err := module.prep(); err != nil {
		return nil, err
	}

	return module, nil
}

type instance interface {
	NetEnv() *netenv.NetEnv
	GetStates() []mgr.StateUpdate
	AddStatesCallback(callbackName string, callback mgr.EventCallbackFunc[mgr.StateUpdate])
}
