package service

import (
	"context"
	"fmt"
	"sync/atomic"
	"time"

	"github.com/safing/portmaster/base/api"
	"github.com/safing/portmaster/base/config"
	"github.com/safing/portmaster/base/database/dbmodule"
	"github.com/safing/portmaster/base/metrics"
	"github.com/safing/portmaster/base/notifications"
	"github.com/safing/portmaster/base/rng"
	"github.com/safing/portmaster/base/runtime"
	"github.com/safing/portmaster/service/broadcasts"
	"github.com/safing/portmaster/service/compat"
	"github.com/safing/portmaster/service/core"
	"github.com/safing/portmaster/service/core/base"
	"github.com/safing/portmaster/service/firewall"
	"github.com/safing/portmaster/service/firewall/interception"
	"github.com/safing/portmaster/service/firewall/interception/dnsmonitor"
	"github.com/safing/portmaster/service/integration"
	"github.com/safing/portmaster/service/intel/customlists"
	"github.com/safing/portmaster/service/intel/filterlists"
	"github.com/safing/portmaster/service/intel/geoip"
	"github.com/safing/portmaster/service/mgr"
	"github.com/safing/portmaster/service/nameserver"
	"github.com/safing/portmaster/service/netenv"
	"github.com/safing/portmaster/service/netquery"
	"github.com/safing/portmaster/service/network"
	"github.com/safing/portmaster/service/process"
	"github.com/safing/portmaster/service/profile"
	"github.com/safing/portmaster/service/resolver"
	"github.com/safing/portmaster/service/status"
	"github.com/safing/portmaster/service/sync"
	"github.com/safing/portmaster/service/ui"
	"github.com/safing/portmaster/service/updates"
	"github.com/safing/portmaster/spn/access"
	"github.com/safing/portmaster/spn/cabin"
	"github.com/safing/portmaster/spn/captain"
	"github.com/safing/portmaster/spn/crew"
	"github.com/safing/portmaster/spn/docks"
	"github.com/safing/portmaster/spn/navigator"
	"github.com/safing/portmaster/spn/patrol"
	"github.com/safing/portmaster/spn/ships"
	"github.com/safing/portmaster/spn/sluice"
	"github.com/safing/portmaster/spn/terminal"
)

// Instance is an instance of a Portmaster service.
type Instance struct {
	ctx          context.Context
	cancelCtx    context.CancelFunc
	serviceGroup *mgr.Group

	exitCode atomic.Int32

	database      *dbmodule.DBModule
	config        *config.Config
	api           *api.API
	metrics       *metrics.Metrics
	runtime       *runtime.Runtime
	notifications *notifications.Notifications
	rng           *rng.Rng
	base          *base.Base

	core         *core.Core
	updates      *updates.Updates
	integration  *integration.OSIntegration
	geoip        *geoip.GeoIP
	netenv       *netenv.NetEnv
	ui           *ui.UI
	profile      *profile.ProfileModule
	network      *network.Network
	netquery     *netquery.NetQuery
	firewall     *firewall.Firewall
	filterLists  *filterlists.FilterLists
	interception *interception.Interception
	dnsmonitor   *dnsmonitor.DNSMonitor
	customlist   *customlists.CustomList
	status       *status.Status
	broadcasts   *broadcasts.Broadcasts
	compat       *compat.Compat
	nameserver   *nameserver.NameServer
	process      *process.ProcessModule
	resolver     *resolver.ResolverModule
	sync         *sync.Sync

	access *access.Access

	// SPN modules
	SpnGroup  *mgr.ExtendedGroup
	cabin     *cabin.Cabin
	navigator *navigator.Navigator
	captain   *captain.Captain
	crew      *crew.Crew
	docks     *docks.Docks
	patrol    *patrol.Patrol
	ships     *ships.Ships
	sluice    *sluice.SluiceModule
	terminal  *terminal.TerminalModule

	CommandLineOperation func() error
}

// New returns a new Portmaster service instance.
func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
	// Create instance to pass it to modules.
	instance := &Instance{}
	instance.ctx, instance.cancelCtx = context.WithCancel(context.Background())

	var err error
	// Base modules
	instance.base, err = base.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create base module: %w", err)
	}
	instance.database, err = dbmodule.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create database module: %w", err)
	}
	instance.config, err = config.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create config module: %w", err)
	}
	instance.api, err = api.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create api module: %w", err)
	}
	instance.metrics, err = metrics.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create metrics module: %w", err)
	}
	instance.runtime, err = runtime.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create runtime module: %w", err)
	}
	instance.notifications, err = notifications.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create runtime module: %w", err)
	}
	instance.rng, err = rng.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create rng module: %w", err)
	}

	// Service modules
	instance.core, err = core.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create core module: %w", err)
	}
	instance.updates, err = updates.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create updates module: %w", err)
	}
	instance.integration, err = integration.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create integration module: %w", err)
	}
	instance.geoip, err = geoip.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create customlist module: %w", err)
	}
	instance.netenv, err = netenv.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create netenv module: %w", err)
	}
	instance.ui, err = ui.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create ui module: %w", err)
	}
	instance.profile, err = profile.NewModule(instance)
	if err != nil {
		return instance, fmt.Errorf("create profile module: %w", err)
	}
	instance.network, err = network.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create network module: %w", err)
	}
	instance.netquery, err = netquery.NewModule(instance)
	if err != nil {
		return instance, fmt.Errorf("create netquery module: %w", err)
	}
	instance.firewall, err = firewall.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create firewall module: %w", err)
	}
	instance.filterLists, err = filterlists.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create filterLists module: %w", err)
	}
	instance.interception, err = interception.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create interception module: %w", err)
	}
	instance.dnsmonitor, err = dnsmonitor.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create dns-listener module: %w", err)
	}
	instance.customlist, err = customlists.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create customlist module: %w", err)
	}
	instance.status, err = status.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create status module: %w", err)
	}
	instance.broadcasts, err = broadcasts.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create broadcasts module: %w", err)
	}
	instance.compat, err = compat.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create compat module: %w", err)
	}
	instance.nameserver, err = nameserver.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create nameserver module: %w", err)
	}
	instance.process, err = process.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create process module: %w", err)
	}
	instance.resolver, err = resolver.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create resolver module: %w", err)
	}
	instance.sync, err = sync.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create sync module: %w", err)
	}
	instance.access, err = access.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create access module: %w", err)
	}

	// SPN modules
	instance.cabin, err = cabin.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create cabin module: %w", err)
	}
	instance.navigator, err = navigator.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create navigator module: %w", err)
	}
	instance.captain, err = captain.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create captain module: %w", err)
	}
	instance.crew, err = crew.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create crew module: %w", err)
	}
	instance.docks, err = docks.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create docks module: %w", err)
	}
	instance.patrol, err = patrol.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create patrol module: %w", err)
	}
	instance.ships, err = ships.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create ships module: %w", err)
	}
	instance.sluice, err = sluice.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create sluice module: %w", err)
	}
	instance.terminal, err = terminal.New(instance)
	if err != nil {
		return instance, fmt.Errorf("create terminal module: %w", err)
	}

	// Add all modules to instance group.
	instance.serviceGroup = mgr.NewGroup(
		instance.base,
		instance.rng,
		instance.database,
		instance.config,
		instance.api,
		instance.metrics,
		instance.runtime,
		instance.notifications,

		instance.core,
		instance.updates,
		instance.integration,
		instance.geoip,
		instance.netenv,

		instance.process,
		instance.profile,
		instance.network,
		instance.netquery,
		instance.firewall,
		instance.nameserver,
		instance.resolver,
		instance.filterLists,
		instance.customlist,
		instance.interception,
		instance.dnsmonitor,

		instance.compat,
		instance.status,
		instance.broadcasts,
		instance.sync,
		instance.ui,

		instance.access,
	)

	// SPN Group
	instance.SpnGroup = mgr.NewExtendedGroup(
		instance.cabin,
		instance.navigator,
		instance.captain,
		instance.crew,
		instance.docks,
		instance.patrol,
		instance.ships,
		instance.sluice,
		instance.terminal,
	)

	return instance, nil
}

// SleepyModule is an interface for modules that can enter some sort of sleep mode.
type SleepyModule interface {
	SetSleep(enabled bool)
}

// SetSleep sets sleep mode on all modules that satisfy the SleepyModule interface.
func (i *Instance) SetSleep(enabled bool) {
	for _, module := range i.serviceGroup.Modules() {
		if sm, ok := module.(SleepyModule); ok {
			sm.SetSleep(enabled)
		}
	}
	for _, module := range i.SpnGroup.Modules() {
		if sm, ok := module.(SleepyModule); ok {
			sm.SetSleep(enabled)
		}
	}
}

// Database returns the database module.
func (i *Instance) Database() *dbmodule.DBModule {
	return i.database
}

// Config returns the config module.
func (i *Instance) Config() *config.Config {
	return i.config
}

// API returns the api module.
func (i *Instance) API() *api.API {
	return i.api
}

// Metrics returns the metrics module.
func (i *Instance) Metrics() *metrics.Metrics {
	return i.metrics
}

// Runtime returns the runtime module.
func (i *Instance) Runtime() *runtime.Runtime {
	return i.runtime
}

// Notifications returns the notifications module.
func (i *Instance) Notifications() *notifications.Notifications {
	return i.notifications
}

// Rng returns the rng module.
func (i *Instance) Rng() *rng.Rng {
	return i.rng
}

// Base returns the base module.
func (i *Instance) Base() *base.Base {
	return i.base
}

// Updates returns the updates module.
func (i *Instance) Updates() *updates.Updates {
	return i.updates
}

// OSIntegration returns the integration module.
func (i *Instance) OSIntegration() *integration.OSIntegration {
	return i.integration
}

// GeoIP returns the geoip module.
func (i *Instance) GeoIP() *geoip.GeoIP {
	return i.geoip
}

// NetEnv returns the netenv module.
func (i *Instance) NetEnv() *netenv.NetEnv {
	return i.netenv
}

// Access returns the access module.
func (i *Instance) Access() *access.Access {
	return i.access
}

// Cabin returns the cabin module.
func (i *Instance) Cabin() *cabin.Cabin {
	return i.cabin
}

// Captain returns the captain module.
func (i *Instance) Captain() *captain.Captain {
	return i.captain
}

// Crew returns the crew module.
func (i *Instance) Crew() *crew.Crew {
	return i.crew
}

// Docks returns the crew module.
func (i *Instance) Docks() *docks.Docks {
	return i.docks
}

// Navigator returns the navigator module.
func (i *Instance) Navigator() *navigator.Navigator {
	return i.navigator
}

// Patrol returns the patrol module.
func (i *Instance) Patrol() *patrol.Patrol {
	return i.patrol
}

// Ships returns the ships module.
func (i *Instance) Ships() *ships.Ships {
	return i.ships
}

// Sluice returns the ships module.
func (i *Instance) Sluice() *sluice.SluiceModule {
	return i.sluice
}

// Terminal returns the terminal module.
func (i *Instance) Terminal() *terminal.TerminalModule {
	return i.terminal
}

// UI returns the ui module.
func (i *Instance) UI() *ui.UI {
	return i.ui
}

// Profile returns the profile module.
func (i *Instance) Profile() *profile.ProfileModule {
	return i.profile
}

// Firewall returns the firewall module.
func (i *Instance) Firewall() *firewall.Firewall {
	return i.firewall
}

// FilterLists returns the filterLists module.
func (i *Instance) FilterLists() *filterlists.FilterLists {
	return i.filterLists
}

// Interception returns the interception module.
func (i *Instance) Interception() *interception.Interception {
	return i.interception
}

// DNSMonitor returns the dns-listener module.
func (i *Instance) DNSMonitor() *dnsmonitor.DNSMonitor {
	return i.dnsmonitor
}

// CustomList returns the customlist module.
func (i *Instance) CustomList() *customlists.CustomList {
	return i.customlist
}

// Status returns the status module.
func (i *Instance) Status() *status.Status {
	return i.status
}

// Broadcasts returns the broadcast module.
func (i *Instance) Broadcasts() *broadcasts.Broadcasts {
	return i.broadcasts
}

// Compat returns the compat module.
func (i *Instance) Compat() *compat.Compat {
	return i.compat
}

// NameServer returns the nameserver module.
func (i *Instance) NameServer() *nameserver.NameServer {
	return i.nameserver
}

// NetQuery returns the netquery module.
func (i *Instance) NetQuery() *netquery.NetQuery {
	return i.netquery
}

// Network returns the network module.
func (i *Instance) Network() *network.Network {
	return i.network
}

// Process returns the process module.
func (i *Instance) Process() *process.ProcessModule {
	return i.process
}

// Resolver returns the resolver module.
func (i *Instance) Resolver() *resolver.ResolverModule {
	return i.resolver
}

// Sync returns the sync module.
func (i *Instance) Sync() *sync.Sync {
	return i.sync
}

// Core returns the core module.
func (i *Instance) Core() *core.Core {
	return i.core
}

// SPNGroup returns the group of all SPN modules.
func (i *Instance) SPNGroup() *mgr.ExtendedGroup {
	return i.SpnGroup
}

// Events

// GetEventSPNConnected return the event manager for the SPN connected event.
func (i *Instance) GetEventSPNConnected() *mgr.EventMgr[struct{}] {
	return i.captain.EventSPNConnected
}

// Special functions

// SetCmdLineOperation sets a command line operation to be executed instead of starting the system. This is useful when functions need all modules to be prepared for a special operation.
func (i *Instance) SetCmdLineOperation(f func() error) {
	i.CommandLineOperation = f
}

// GetStates returns the current states of all group modules.
func (i *Instance) GetStates() []mgr.StateUpdate {
	mainStates := i.serviceGroup.GetStates()
	spnStates := i.SpnGroup.GetStates()

	updates := make([]mgr.StateUpdate, 0, len(mainStates)+len(spnStates))
	updates = append(updates, mainStates...)
	updates = append(updates, spnStates...)

	return updates
}

// AddStatesCallback adds the given callback function to all group modules that
// expose a state manager at States().
func (i *Instance) AddStatesCallback(callbackName string, callback mgr.EventCallbackFunc[mgr.StateUpdate]) {
	i.serviceGroup.AddStatesCallback(callbackName, callback)
	i.SpnGroup.AddStatesCallback(callbackName, callback)
}

// Ready returns whether all modules in the main service module group have been started and are still running.
func (i *Instance) Ready() bool {
	return i.serviceGroup.Ready()
}

// Ctx returns the instance context.
// It is only canceled on shutdown.
func (i *Instance) Ctx() context.Context {
	return i.ctx
}

// Start starts the instance.
func (i *Instance) Start() error {
	return i.serviceGroup.Start()
}

// Stop stops the instance and cancels the instance context when done.
func (i *Instance) Stop() error {
	defer i.cancelCtx()
	return i.serviceGroup.Stop()
}

// RestartExitCode will instruct portmaster-start to restart the process immediately, potentially with a new version.
const RestartExitCode = 23

// Restart asynchronously restarts the instance.
// This only works if the underlying system/process supports this.
func (i *Instance) Restart() {
	// Send a restart event, give it 10ms extra to propagate.
	i.core.EventRestart.Submit(struct{}{})
	time.Sleep(10 * time.Millisecond)

	i.shutdown(RestartExitCode)
}

// Shutdown asynchronously stops the instance.
func (i *Instance) Shutdown() {
	// Send a shutdown event, give it 10ms extra to propagate.
	i.core.EventShutdown.Submit(struct{}{})
	time.Sleep(10 * time.Millisecond)

	i.shutdown(0)
}

func (i *Instance) shutdown(exitCode int) {
	// Set given exit code.
	i.exitCode.Store(int32(exitCode))

	m := mgr.New("instance")
	m.Go("shutdown", func(w *mgr.WorkerCtx) error {
		for {
			if err := i.Stop(); err != nil {
				w.Error("failed to shutdown", "err", err, "retry", "1s")
				time.Sleep(1 * time.Second)
			} else {
				return nil
			}
		}
	})
}

// Stopping returns whether the instance is shutting down.
func (i *Instance) Stopping() bool {
	return i.ctx.Err() != nil
}

// Stopped returns a channel that is triggered when the instance has shut down.
func (i *Instance) Stopped() <-chan struct{} {
	return i.ctx.Done()
}

// ExitCode returns the set exit code of the instance.
func (i *Instance) ExitCode() int {
	return int(i.exitCode.Load())
}