package workspace

import (
	"errors"

	"cloud.o-forge.io/core/oc-lib/dbs"
	"cloud.o-forge.io/core/oc-lib/logs"
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area"
	"cloud.o-forge.io/core/oc-lib/models/peer"
	"cloud.o-forge.io/core/oc-lib/models/utils"
	"cloud.o-forge.io/core/oc-lib/tools"
)

// Workspace is a struct that represents a workspace
type workspaceMongoAccessor struct {
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
}

// New creates a new instance of the workspaceMongoAccessor
func NewAccessorHistory(request *tools.APIRequest) *workspaceMongoAccessor {
	return new(tools.WORKSPACE_HISTORY, request)
}

func NewAccessor(request *tools.APIRequest) *workspaceMongoAccessor {
	return new(tools.WORKSPACE, request)
}

// New creates a new instance of the workspaceMongoAccessor
func new(t tools.DataType, request *tools.APIRequest) *workspaceMongoAccessor {
	return &workspaceMongoAccessor{
		utils.AbstractAccessor{
			Logger:  logs.CreateLogger(t.String()), // Create a logger with the data type
			Request: request,
			Type:    t,
		},
	}
}

// DeleteOne deletes a workspace from the database, given its ID, it automatically share to peers if the workspace is shared
// it checks if a workspace with the same name already exists
func (a *workspaceMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
	res, code, err := utils.GenericDeleteOne(id, a)
	if code == 200 && res != nil {
		a.share(res.(*Workspace), tools.DELETE, a.GetCaller()) // Share the deletion to the peers
	}
	return res, code, err
}

// UpdateOne updates a workspace in the database, given its ID, it automatically share to peers if the workspace is shared
func (a *workspaceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
	d := set.(*Workspace) // Get the workspace from the set
	d.Clear()
	if d.Active { // If the workspace is active, deactivate all the other workspaces
		res, _, err := a.LoadAll(true)
		if err == nil {
			for _, r := range res {
				if r.GetID() != id {
					r.(*Workspace).Active = false
					a.UpdateOne(r.(*Workspace), r.GetID())
				}
			}
		}
	}
	res, code, err := utils.GenericUpdateOne(set, id, a, &Workspace{})
	if code == 200 && res != nil {
		a.share(res.(*Workspace), tools.PUT, a.GetCaller())
	}
	return res, code, err
}

// StoreOne stores a workspace in the database, it checks if a workspace with the same name already exists
func (a *workspaceMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
	filters := &dbs.Filters{
		Or: map[string][]dbs.Filter{
			"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: data.GetName() + "_workspace"}},
		},
	}
	res, _, err := a.Search(filters, "", true) // Search for the workspace
	if err == nil && len(res) > 0 {            // If the workspace already exists, return an error
		return nil, 409, errors.New("a workspace with the same name already exists")
	}
	// reset the resources
	d := data.(*Workspace)
	d.Clear()
	return utils.GenericStoreOne(d, a)
}

// CopyOne copies a workspace in the database
func (a *workspaceMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
	return utils.GenericStoreOne(data, a)
}

func (a *workspaceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
	return utils.GenericLoadOne[*Workspace](id, func(d utils.DBObject) (utils.DBObject, int, error) {
		d.(*Workspace).Fill(a.GetRequest())
		return d, 200, nil
	}, a)
}

func (a *workspaceMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
	return utils.GenericLoadAll[*Workspace](func(d utils.DBObject) utils.ShallowDBObject {
		d.(*Workspace).Fill(a.GetRequest())
		return d
	}, isDraft, a)
}

func (a *workspaceMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
	return utils.GenericSearch[*Workspace](filters, search, (&Workspace{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject {
		d.(*Workspace).Fill(a.GetRequest())
		return d
	}, isDraft, a)
}

/*
This function is used to share the workspace with the peers
*/
func (a *workspaceMongoAccessor) share(realData *Workspace, method tools.METHOD, caller *tools.HTTPCaller) {
	if realData == nil || realData.Shared == "" || caller == nil || caller.Disabled {
		return
	}
	shallow := &shallow_collaborative_area.ShallowCollaborativeArea{}
	access := (shallow).GetAccessor(a.GetRequest())
	res, code, _ := access.LoadOne(realData.Shared)
	if code != 200 {
		return
	}
	var err error
	paccess := &peer.Peer{}
	for _, p := range res.(*shallow_collaborative_area.ShallowCollaborativeArea).Peers {
		paccess.UUID = p
		if ok, _ := paccess.IsMySelf(); ok { // If the peer is the current peer, never share because it will create a loop
			continue
		}
		if method == tools.DELETE { // If the workspace is deleted, share the deletion
			history := NewHistory()
			history.StoreOne(history.MapFromWorkspace(res.(*Workspace)))
			_, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKSPACE, tools.DELETE, map[string]interface{}{}, caller)
		} else { // If the workspace is updated, share the update
			_, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKSPACE, tools.PUT, res.Serialize(res), caller)
		}
	}
	if err != nil {
		a.Logger.Error().Msg(err.Error())
	}
}
