package server

import (
	"context"
	"time"

	"emperror.dev/errors"

	"github.com/Tech-Gamer/nwy-wings/internal/database"
	"github.com/Tech-Gamer/nwy-wings/internal/models"
)

const ActivityPowerPrefix = "server:power."

const (
	ActivityConsoleCommand      = models.Event("server:console.command")
	ActivitySftpWrite           = models.Event("server:sftp.write")
	ActivitySftpCreate          = models.Event("server:sftp.create")
	ActivitySftpCreateDirectory = models.Event("server:sftp.create-directory")
	ActivitySftpRename          = models.Event("server:sftp.rename")
	ActivitySftpDelete          = models.Event("server:sftp.delete")
	ActivityFileUploaded        = models.Event("server:file.uploaded")
)

// RequestActivity is a wrapper around a LoggedEvent that is able to track additional request
// specific metadata including the specific user and IP address associated with all subsequent
// events. The internal logged event structure can be extracted by calling RequestEvent.Event().
type RequestActivity struct {
	server string
	user   string
	ip     string
}

// Event returns the underlying logged event from the RequestEvent instance and sets the
// specific event and metadata on it.
func (ra RequestActivity) Event(event models.Event, metadata models.ActivityMeta) *models.Activity {
	a := models.Activity{Server: ra.server, IP: ra.ip, Event: event, Metadata: metadata}

	return a.SetUser(ra.user)
}

// SetUser clones the RequestActivity struct and sets a new user value on the copy
// before returning it.
func (ra RequestActivity) SetUser(u string) RequestActivity {
	c := ra
	c.user = u
	return c
}

func (s *Server) NewRequestActivity(user string, ip string) RequestActivity {
	return RequestActivity{server: s.ID(), user: user, ip: ip}
}

// SaveActivity saves an activity entry to the database in a background routine. If an error is
// encountered it is logged but not returned to the caller.
func (s *Server) SaveActivity(a RequestActivity, event models.Event, metadata models.ActivityMeta) {
	ctx, cancel := context.WithTimeout(s.Context(), time.Second*3)
	go func() {
		defer cancel()
		if tx := database.Instance().WithContext(ctx).Create(a.Event(event, metadata)); tx.Error != nil {
			s.Log().WithField("error", errors.WithStack(tx.Error)).
				WithField("event", event).
				Error("activity: failed to save event")
		}
	}()
}
