package collaborative_area

import (
	"fmt"
	"slices"

	"cloud.o-forge.io/core/oc-lib/dbs"
	"cloud.o-forge.io/core/oc-lib/dbs/mongo"
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area/rules/rule"
	"cloud.o-forge.io/core/oc-lib/models/peer"
	"cloud.o-forge.io/core/oc-lib/models/utils"
	w "cloud.o-forge.io/core/oc-lib/models/workflow"
	"cloud.o-forge.io/core/oc-lib/models/workspace"
	"cloud.o-forge.io/core/oc-lib/static"
	"cloud.o-forge.io/core/oc-lib/tools"
)

// SharedWorkspace is a struct that represents a collaborative area
type collaborativeAreaMongoAccessor struct {
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
}

// New creates a new instance of the collaborativeAreaMongoAccessor
func New() *collaborativeAreaMongoAccessor {
	return &collaborativeAreaMongoAccessor{}
}

// DeleteOne deletes a collaborative area from the database, given its ID, it automatically share to peers if the workspace is shared
func (wfa *collaborativeAreaMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
	set, code, _ := wfa.LoadOne(id)
	if code == 200 { // always delete on peers than recreate
		wfa.deleteToPeer(set.(*CollaborativeArea))
	}
	wfa.sharedWorkflow(&CollaborativeArea{}, id)  // create all shared workflows
	wfa.sharedWorkspace(&CollaborativeArea{}, id) // create all collaborative areas
	return wfa.GenericDeleteOne(id, wfa)          // then add on yours
}

/*
sharedWorkspace is a function that shares the collaborative area to the peers
*/
func (wfa *collaborativeAreaMongoAccessor) sharedWorkspace(shared *CollaborativeArea, id string) {
	eldest, code, _ := wfa.LoadOne(id) // get the eldest
	accessor := (&workspace.Workspace{}).GetAccessor(nil)
	if code == 200 {
		eld := eldest.(*CollaborativeArea)
		if eld.Workspaces != nil { // update all your workspaces in the eldest by replacing shared ref by an empty string
			for _, v := range eld.Workspaces {
				accessor.UpdateOne(&workspace.Workspace{Shared: ""}, v)
				if wfa.Caller != nil || wfa.Caller.URLS == nil || wfa.Caller.URLS[tools.WORKSPACE] == nil {
					continue
				}
				paccess := (&peer.Peer{})        // send to all peers
				for _, p := range shared.Peers { // delete the collaborative area on the peer
					b, err := paccess.LaunchPeerExecution(p, v, tools.WORKSPACE, tools.DELETE, nil, wfa.Caller)
					if err != nil && b == nil {
						wfa.Logger.Error().Msg("Could not send to peer " + p + ". Error: " + err.Error())
					}
				}
			}
		}
	}
	if shared.Workspaces != nil {
		for _, v := range shared.Workspaces { // update all the collaborative areas
			workspace, code, _ := accessor.UpdateOne(&workspace.Workspace{Shared: shared.UUID}, v) // add the shared ref to workspace
			if wfa.Caller != nil || wfa.Caller.URLS == nil || wfa.Caller.URLS[tools.WORKSPACE] == nil {
				continue
			}
			for _, p := range shared.Peers {
				if code != 200 {
					continue
				}
				paccess := (&peer.Peer{}) // send to all peers, add the collaborative area on the peer
				s := workspace.Serialize()
				s["name"] = fmt.Sprintf("%v", s["name"]) + "_" + p
				b, err := paccess.LaunchPeerExecution(p, v, tools.WORKSPACE, tools.POST, s, wfa.Caller)
				if err != nil && b == nil {
					wfa.Logger.Error().Msg("Could not send to peer " + p + ". Error: " + err.Error())
				}
			}
		}
	}
	// deleting on peers before adding, to avoid conflicts on peers side
	// because you have no reference to the remote collaborative area
}

// sharedWorkflow is a function that shares the shared workflow to the peers
func (wfa *collaborativeAreaMongoAccessor) sharedWorkflow(shared *CollaborativeArea, id string) {
	accessor := (&w.Workflow{}).GetAccessor(nil)
	eldest, code, _ := wfa.LoadOne(id) // get the eldest
	if code == 200 {
		eld := eldest.(*CollaborativeArea)
		if eld.Workflows != nil {
			for _, v := range eld.Workflows {
				data, code, _ := accessor.LoadOne(v)
				if code == 200 {
					s := data.(*w.Workflow)
					new := []string{}
					for _, id2 := range s.Shared {
						if id2 != id {
							new = append(new, id2)
						}
					} // kick the shared reference in your old shared workflow
					n := &w.Workflow{}
					n.Shared = new
					accessor.UpdateOne(n, v)
					if wfa.Caller != nil || wfa.Caller.URLS == nil || wfa.Caller.URLS[tools.WORKFLOW] == nil {
						continue
					}
					paccess := (&peer.Peer{})        // send to all peers
					for _, p := range shared.Peers { // delete the shared workflow on the peer
						b, err := paccess.LaunchPeerExecution(p, v, tools.WORKFLOW, tools.DELETE, nil, wfa.Caller)
						if err != nil && b == nil {
							wfa.Logger.Error().Msg("Could not send to peer " + p + ". Error: " + err.Error())
						}
					}
				}
			}
		}
	}
	if shared.Workflows != nil { // update all the shared workflows
		for _, v := range shared.Workflows {
			data, code, _ := accessor.LoadOne(v)
			if code == 200 {
				s := data.(*w.Workflow)
				if !slices.Contains(s.Shared, id) {
					s.Shared = append(s.Shared, id)
					workflow, code, _ := accessor.UpdateOne(s, v)
					if wfa.Caller != nil || wfa.Caller.URLS == nil || wfa.Caller.URLS[tools.WORKFLOW] == nil {
						continue
					}
					paccess := (&peer.Peer{})
					for _, p := range shared.Peers { // send to all peers
						if code == 200 {
							s := workflow.Serialize() // add the shared workflow on the peer
							s["name"] = fmt.Sprintf("%v", s["name"]) + "_" + p
							b, err := paccess.LaunchPeerExecution(p, shared.UUID, tools.WORKFLOW, tools.POST, s, wfa.Caller)
							if err != nil && b == nil {
								wfa.Logger.Error().Msg("Could not send to peer " + p + ". Error: " + err.Error())
							}
						}
					}
				}
			}
		}
	}
	// deleting on peers before adding, to avoid conflicts on peers side
	// because you have no reference to the remote shared workflow
}

// sharedWorkspace is a function that shares the collaborative area to the peers
func (wfa *collaborativeAreaMongoAccessor) deleteToPeer(shared *CollaborativeArea) {
	if wfa.Caller == nil || wfa.Caller.URLS == nil || wfa.Caller.URLS[tools.COLLABORATIVE_AREA] == nil || wfa.Caller.Disabled {
		return
	}
	paccess := (&peer.Peer{})
	for _, v := range shared.Peers {
		if (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: v}}).IsMySelf() {
			continue
		}
		b, err := paccess.LaunchPeerExecution(v, shared.UUID, tools.COLLABORATIVE_AREA, tools.DELETE, nil, wfa.Caller)
		if err != nil && b == nil {
			wfa.Logger.Error().Msg("Could not send to peer " + v + ". Error: " + err.Error())
		}
	}
}

// sharedWorkspace is a function that shares the collaborative area to the peers
func (wfa *collaborativeAreaMongoAccessor) sendToPeer(shared *CollaborativeArea) {
	if wfa.Caller == nil || wfa.Caller.URLS == nil || wfa.Caller.URLS[tools.COLLABORATIVE_AREA] == nil || wfa.Caller.Disabled {
		return
	}

	paccess := (&peer.Peer{})
	for _, v := range shared.Peers {
		if (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: v}}).IsMySelf() || shared.IsSent {
			continue
		}
		shared.IsSent = true
		b, err := paccess.LaunchPeerExecution(v, v, tools.COLLABORATIVE_AREA, tools.POST, shared.Serialize(), wfa.Caller)
		if err != nil && b == nil {
			wfa.Logger.Error().Msg("Could not send to peer " + v + ". Error: " + err.Error())
		}
	}
}

// UpdateOne updates a collaborative area in the database, given its ID and the new data, it automatically share to peers if the workspace is shared
func (wfa *collaborativeAreaMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
	res, code, err := wfa.GenericUpdateOne(set.(*CollaborativeArea), id, wfa, &CollaborativeArea{})
	wfa.deleteToPeer(res.(*CollaborativeArea))        // delete the collaborative area on the peer
	wfa.sharedWorkflow(res.(*CollaborativeArea), id)  // replace all shared workflows
	wfa.sharedWorkspace(res.(*CollaborativeArea), id) // replace all collaborative areas (not shared worspace obj but workspace one)
	wfa.sendToPeer(res.(*CollaborativeArea))          // send the collaborative area (collaborative area object) to the peers
	return res, code, err
}

// StoreOne stores a collaborative area in the database, it automatically share to peers if the workspace is shared
func (wfa *collaborativeAreaMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
	id, _ := static.GetMyLocalJsonPeer()                                          // get the local peer
	data.(*CollaborativeArea).CreatorID = id                                      // set the creator id
	data.(*CollaborativeArea).Peers = append(data.(*CollaborativeArea).Peers, id) // add the creator id to the peers
	// then reset the shared fields
	if data.(*CollaborativeArea).Workspaces == nil {
		data.(*CollaborativeArea).Workspaces = []string{}
	}
	if data.(*CollaborativeArea).Workflows == nil {
		data.(*CollaborativeArea).Workflows = []string{}
	}
	if data.(*CollaborativeArea).Rules == nil {
		data.(*CollaborativeArea).Rules = []string{}
	}

	d, code, err := wfa.GenericStoreOne(data.(*CollaborativeArea), wfa)

	if code == 200 {
		wfa.sharedWorkflow(d.(*CollaborativeArea), d.GetID())  // create all shared workflows
		wfa.sharedWorkspace(d.(*CollaborativeArea), d.GetID()) // create all collaborative areas
		wfa.sendToPeer(d.(*CollaborativeArea))                 // send the collaborative area (collaborative area object) to the peers
	}
	return data, code, err
}

// CopyOne copies a CollaborativeArea in the database
func (wfa *collaborativeAreaMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
	return wfa.StoreOne(data)
}

// enrich is a function that enriches the CollaborativeArea with the shared objects
func (wfa *collaborativeAreaMongoAccessor) enrich(sharedWorkspace *CollaborativeArea) *CollaborativeArea {
	access := (&workspace.Workspace{}).GetAccessor(nil)
	res, code, _ := access.Search(&dbs.Filters{
		Or: map[string][]dbs.Filter{
			"abstractobject.id": {{Operator: dbs.IN.String(), Value: sharedWorkspace.Workspaces}},
		},
	}, "")
	if code == 200 {
		for _, r := range res {
			sharedWorkspace.SharedWorkspaces = append(sharedWorkspace.SharedWorkspaces, r.(*workspace.Workspace))
		}
	}
	access = (&w.Workflow{}).GetAccessor(nil)
	res, code, _ = access.Search(&dbs.Filters{
		Or: map[string][]dbs.Filter{
			"abstractobject.id": {{Operator: dbs.IN.String(), Value: sharedWorkspace.Workflows}},
		},
	}, "")
	if code == 200 {
		for _, r := range res {
			sharedWorkspace.SharedWorkflows = append(sharedWorkspace.SharedWorkflows, r.(*w.Workflow))
		}
	}
	access = (&peer.Peer{}).GetAccessor(nil)
	res, code, _ = access.Search(&dbs.Filters{
		Or: map[string][]dbs.Filter{
			"abstractobject.id": {{Operator: dbs.IN.String(), Value: sharedWorkspace.Peers}},
		},
	}, "")
	if code == 200 {
		for _, r := range res {
			sharedWorkspace.SharedPeers = append(sharedWorkspace.SharedPeers, r.(*peer.Peer))
		}
	}
	access = (&rule.Rule{}).GetAccessor(nil)
	res, code, _ = access.Search(&dbs.Filters{
		Or: map[string][]dbs.Filter{
			"abstractobject.id": {{Operator: dbs.IN.String(), Value: sharedWorkspace.Rules}},
		},
	}, "")
	if code == 200 {
		for _, r := range res {
			sharedWorkspace.SharedRules = append(sharedWorkspace.SharedRules, r.(*rule.Rule))
		}
	}
	return sharedWorkspace
}

// LoadOne loads a collaborative area from the database, given its ID and enrich it
func (wfa *collaborativeAreaMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
	var sharedWorkspace CollaborativeArea
	res_mongo, code, err := mongo.MONGOService.LoadOne(id, wfa.GetType())
	if err != nil {
		wfa.Logger.Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error())
		return nil, code, err
	}
	res_mongo.Decode(&sharedWorkspace)
	return wfa.enrich(&sharedWorkspace), 200, nil // enrich the collaborative area
}

// LoadAll loads all the collaborative areas from the database and enrich them
func (wfa collaborativeAreaMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) {
	objs := []utils.ShallowDBObject{}
	res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType())
	if err != nil {
		wfa.Logger.Error().Msg("Could not retrieve any from db. Error: " + err.Error())
		return nil, code, err
	}
	var results []CollaborativeArea
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
		return nil, 404, err
	}
	for _, r := range results {
		objs = append(objs, wfa.enrich(&r)) // enrich the collaborative area
	}
	return objs, 200, nil
}

// Search searches for collaborative areas in the database, given some filters OR a search string
func (wfa *collaborativeAreaMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) {
	objs := []utils.ShallowDBObject{}
	if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" {
		filters = &dbs.Filters{
			Or: map[string][]dbs.Filter{ // search by name only by default can be override
				"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
			},
		}
	}
	res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType())
	if err != nil {
		wfa.Logger.Error().Msg("Could not store to db. Error: " + err.Error())
		return nil, code, err
	}
	var results []CollaborativeArea
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
		return nil, 404, err
	}
	for _, r := range results {
		objs = append(objs, wfa.enrich(&r)) // enrich the collaborative area
	}
	return objs, 200, nil
}
