package status

import (
	"fmt"

	"github.com/safing/portbase/database/record"
	"github.com/safing/portbase/runtime"
	"github.com/safing/portmaster/netenv"
)

var pushUpdate runtime.PushFunc

func setupRuntimeProvider() (err error) {
	// register the system status getter
	//
	statusProvider := runtime.SimpleValueGetterFunc(func(_ string) ([]record.Record, error) {
		return []record.Record{buildSystemStatus()}, nil
	})
	pushUpdate, err = runtime.Register("system/status", statusProvider)
	if err != nil {
		return err
	}

	// register the selected security level setter
	//
	levelProvider := runtime.SimpleValueSetterFunc(setSelectedSecurityLevel)
	_, err = runtime.Register("system/security-level", levelProvider)
	if err != nil {
		return err
	}

	return nil
}

// setSelectedSecurityLevel updates the selected security level.
func setSelectedSecurityLevel(r record.Record) (record.Record, error) {
	var upd *SelectedSecurityLevelRecord
	if r.IsWrapped() {
		upd = new(SelectedSecurityLevelRecord)
		if err := record.Unwrap(r, upd); err != nil {
			return nil, err
		}
	} else {
		// TODO(ppacher): this can actually never happen
		// as we're write-only and ValueProvider.Set() should
		// only ever be called from the HTTP API (so r must be wrapped).
		// Though, make sure we handle the case as well ...
		var ok bool
		upd, ok = r.(*SelectedSecurityLevelRecord)
		if !ok {
			return nil, fmt.Errorf("expected *SelectedSecurityLevelRecord but got %T", r)
		}
	}

	// if the network rating system is not used at all we always force the security
	// level to trusted.
	if !NetworkRatingEnabled() {
		upd.SelectedSecurityLevel = SecurityLevelNormal
	}

	if !IsValidSecurityLevel(upd.SelectedSecurityLevel) {
		return nil, fmt.Errorf("invalid security level: %d", upd.SelectedSecurityLevel)
	}

	if SelectedSecurityLevel() != upd.SelectedSecurityLevel {
		setSelectedLevel(upd.SelectedSecurityLevel)
		triggerAutopilot()
	}

	return r, nil
}

// buildSystemStatus build a new system status record.
func buildSystemStatus() *SystemStatusRecord {
	status := &SystemStatusRecord{
		ActiveSecurityLevel:   ActiveSecurityLevel(),
		SelectedSecurityLevel: SelectedSecurityLevel(),
		ThreatMitigationLevel: getHighestMitigationLevel(),
		CaptivePortal:         netenv.GetCaptivePortal(),
		OnlineStatus:          netenv.GetOnlineStatus(),
	}

	status.CreateMeta()
	status.SetKey("runtime:system/status")

	return status
}

// pushSystemStatus pushes a new system status via
// the runtime database.
func pushSystemStatus() {
	if pushUpdate == nil {
		return
	}

	record := buildSystemStatus()
	record.Lock()
	defer record.Unlock()

	pushUpdate(record)
}
