package profile

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/safing/portmaster/service/intel/filterlists"
	"github.com/safing/portmaster/service/mgr"
	"github.com/safing/portmaster/service/profile/endpoints"
)

var (
	cfgLock sync.RWMutex

	cfgDefaultAction       uint8
	cfgEndpoints           endpoints.Endpoints
	cfgServiceEndpoints    endpoints.Endpoints
	cfgSplitTunUsagePolicy endpoints.Endpoints
	cfgSPNUsagePolicy      endpoints.Endpoints
	cfgSPNTransitHubPolicy endpoints.Endpoints
	cfgSPNExitHubPolicy    endpoints.Endpoints
	cfgFilterLists         []string
)

func registerGlobalConfigProfileUpdater() error {
	module.instance.Config().EventConfigChange.AddCallback("update global config profile", func(wc *mgr.WorkerCtx, s struct{}) (cancel bool, err error) {
		return false, updateGlobalConfigProfile(wc.Ctx())
	})

	return nil
}

const globalConfigProfileErrorID = "profile:global-profile-error"

func updateGlobalConfigProfile(_ context.Context) error {
	cfgLock.Lock()
	defer cfgLock.Unlock()

	var err error
	var lastErr error

	action := cfgOptionDefaultAction()
	switch action {
	case DefaultActionPermitValue:
		cfgDefaultAction = DefaultActionPermit
	case DefaultActionAskValue:
		cfgDefaultAction = DefaultActionAsk
	case DefaultActionBlockValue:
		cfgDefaultAction = DefaultActionBlock
	default:
		// TODO: module error?
		lastErr = fmt.Errorf(`default action "%s" invalid`, action)
		cfgDefaultAction = DefaultActionBlock // default to block in worst case
	}

	list := cfgOptionEndpoints()
	cfgEndpoints, err = endpoints.ParseEndpoints(list)
	if err != nil {
		// TODO: module error?
		lastErr = err
	}

	list = cfgOptionServiceEndpoints()
	cfgServiceEndpoints, err = endpoints.ParseEndpoints(list)
	if err != nil {
		// TODO: module error?
		lastErr = err
	}

	list = cfgOptionFilterLists()
	cfgFilterLists, err = filterlists.ResolveListIDs(list)
	if err != nil {
		lastErr = err
	}

	list = cfgOptionSplitTunUsagePolicy()
	cfgSplitTunUsagePolicy, err = endpoints.ParseEndpoints(list)
	if err != nil {
		// TODO: module error?
		lastErr = err
	}

	list = cfgOptionSPNUsagePolicy()
	cfgSPNUsagePolicy, err = endpoints.ParseEndpoints(list)
	if err != nil {
		// TODO: module error?
		lastErr = err
	}

	list = cfgOptionTransitHubPolicy()
	cfgSPNTransitHubPolicy, err = endpoints.ParseEndpoints(list)
	if err != nil {
		// TODO: module error?
		lastErr = err
	}

	list = cfgOptionExitHubPolicy()
	cfgSPNExitHubPolicy, err = endpoints.ParseEndpoints(list)
	if err != nil {
		// TODO: module error?
		lastErr = err
	}

	// Build config.
	newConfig := make(map[string]interface{})
	// fill profile config options
	for key, value := range cfgStringOptions {
		newConfig[key] = value()
	}
	for key, value := range cfgStringArrayOptions {
		newConfig[key] = value()
	}
	for key, value := range cfgIntOptions {
		newConfig[key] = value()
	}
	for key, value := range cfgBoolOptions {
		newConfig[key] = value()
	}

	// Build global profile for reference.
	profile := New(&Profile{
		ID:       "global-config",
		Source:   SourceSpecial,
		Name:     "Global Configuration",
		Config:   newConfig,
		Internal: true,
	})

	// save profile
	err = profile.Save()
	if err != nil && lastErr == nil {
		// other errors are more important
		lastErr = err
	}

	// If there was any error, try again later until it succeeds.
	if lastErr == nil {
		module.states.Remove(globalConfigProfileErrorID)
	} else {
		// Create task after first failure.

		// Schedule task.
		_ = module.mgr.Delay("retry updating global config profile", 15*time.Second,
			func(w *mgr.WorkerCtx) error {
				return updateGlobalConfigProfile(w.Ctx())
			})

		// Add module warning to inform user.
		module.states.Add(mgr.State{
			ID:      globalConfigProfileErrorID,
			Name:    "Internal Settings Failure",
			Message: fmt.Sprintf("Some global settings might not be applied correctly. You can try restarting the Portmaster to resolve this problem. Error: %s", lastErr),
			Type:    mgr.StateTypeWarning,
		})
	}

	return lastErr
}
