package events

import (
	"agent/client"
	"agent/commons/debug"
	"agent/commons/utime"
	"agent/modules/commands/api"
	ecmds "agent/modules/commands/cmds"
	"agent/modules/commands/utils"
	"context"
	"sync"
)

type commandsModule struct {
	lastEventIndex int64
	lastRequest    utime.Time
}

func (m *commandsModule) Init(ctx context.Context) (err error) {
	m.lastEventIndex = 0
	m.lastRequest = utime.Time{}
	return nil
}

func (m *commandsModule) Name() string {
	return "Commands"
}

func (m *commandsModule) Commands() []client.ICommand {
	return ecmds.InitItems
}

func (m *commandsModule) Run(ctx context.Context, wg *sync.WaitGroup) {
	defer func() {
		wg.Done()
	}()

	cl := client.ExtractClient(ctx)

	for {
		if !cl.IsAuthorized() {
			utime.Sleep(1 * utime.Second)
			continue
		}

		if !m.canRequest(cl) {
			utime.Sleep(1 * utime.Second)
			continue
		}

		nextEvents, err := m.nextEvents(ctx, cl)
		if err != nil {
			debug.LogErrf("AgentNextEvents: %s", err)
			continue
		}

		if cl.Config().Version < nextEvents.Extra.ConfigVersion {
			debug.Logf(
				"Config version mismatch: client %v, server %v",
				cl.Config().Version, nextEvents.Extra.ConfigVersion,
			)
			if err := cl.DownloadConfig(ctx); err != nil {
				debug.Logf("Failed to download config: %s", err)
				continue
			}
			debug.Logf(
				`Config reloaded successfully, new version: %v`,
				cl.Config().Version,
			)
		}

		debug.Logf(
			"Events: %d, Received: %d, Errors: %d",
			len(nextEvents.Events), len(nextEvents.Status.ReceivedUuids), nextEvents.Status.Errors,
		)

		if nextEvents.Status.LastIndex > m.lastEventIndex {
			m.lastEventIndex = nextEvents.Status.LastIndex
		}

		var results, errors []utils.ResultEventItem
		for _, rawEvent := range nextEvents.Events {
			eventIn, errEvent := utils.RawToEvent(ctx, &rawEvent)
			if errEvent != nil {
				errors = append(errors, utils.ResultEventItem{EventIn: rawEvent, EventOut: errEvent})
				continue
			}
			if len(eventIn.Category()) == 0 {
				debug.LogErr("Unknonw Error: debug")
				continue
			}
			if eventOut, errEvent := utils.Execute(ctx, eventIn); errEvent != nil {
				errors = append(errors, utils.ResultEventItem{EventIn: rawEvent, EventOut: errEvent})
			} else {
				results = append(results, utils.ResultEventItem{EventIn: rawEvent, EventOut: eventOut})
			}
		}

		debug.Logf("Results: %d, Errors: %d", len(results), len(errors))

		if results = append(results, errors...); len(results) > 0 {
			utils.SetResults(ctx, cl, results)
		}
	}
}

func (m *commandsModule) OnLogin(ctx context.Context, config map[string]any) (err error) {
	return nil
}

func (m *commandsModule) nextEvents(ctx context.Context, cl client.IClient) (res api.AgentNextEventsResult, err error) {
	defer func() {
		m.lastRequest = utime.Now()
	}()
	return api.AgentNextEvents(ctx, cl,
		m.lastEventIndex,
		api.AgentNextEventsOpts{IncludeReceived: true},
	)
}

func (m *commandsModule) canRequest(cl client.IClient) bool {
	nextEventInterval := cl.Interval().Next
	if utils.ForceNextEventInterval > 0 {
		nextEventInterval = utils.ForceNextEventInterval
	} else if nextEventInterval == 0 {
		nextEventInterval = utils.DefaultNextEventInterval
	}
	if m.lastRequest.IsZero() {
		return true
	}
	return utime.Now().After(m.lastRequest.Add(utime.Duration(nextEventInterval) * utime.Second))
}
