package collaborative_area

import (
	"fmt"
	"slices"

	"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/rules/rule"
	"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/models/workflow"
	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/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)

	workspaceAccessor utils.Accessor
	workflowAccessor  utils.Accessor
	peerAccessor      utils.Accessor
	ruleAccessor      utils.Accessor
}

func NewAccessor(request *tools.APIRequest) *collaborativeAreaMongoAccessor {
	return &collaborativeAreaMongoAccessor{
		AbstractAccessor: utils.AbstractAccessor{
			Logger:  logs.CreateLogger(tools.COLLABORATIVE_AREA.String()), // Create a logger with the data type
			Request: request,
			Type:    tools.COLLABORATIVE_AREA,
		},
		workspaceAccessor: (&workspace.Workspace{}).GetAccessor(request),
		workflowAccessor:  (&w.Workflow{}).GetAccessor(request),
		peerAccessor:      (&peer.Peer{}).GetAccessor(request),
		ruleAccessor:      (&rule.Rule{}).GetAccessor(request),
	}
}

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

// 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 (a *collaborativeAreaMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
	res, code, err := utils.GenericUpdateOne(set.(*CollaborativeArea).Trim(), id, a, &CollaborativeArea{})
	// a.deleteToPeer(res.(*CollaborativeArea))        // delete the collaborative area on the peer
	a.sharedWorkflow(res.(*CollaborativeArea), id)  // replace all shared workflows
	a.sharedWorkspace(res.(*CollaborativeArea), id) // replace all collaborative areas (not shared worspace obj but workspace one)
	// a.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 (a *collaborativeAreaMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
	_, id := (&peer.Peer{}).IsMySelf()  // get the local peer
	data.(*CollaborativeArea).Clear(id) // set the creator
	// retrieve or proper peer
	if data.(*CollaborativeArea).CollaborativeAreaRule != nil {
		data.(*CollaborativeArea).CollaborativeAreaRule = &CollaborativeAreaRule{}
	}
	data.(*CollaborativeArea).CollaborativeAreaRule.Creator = id
	d, code, err := utils.GenericStoreOne(data.(*CollaborativeArea).Trim(), a)
	if code == 200 {
		a.sharedWorkflow(d.(*CollaborativeArea), d.GetID())  // create all shared workflows
		a.sharedWorkspace(d.(*CollaborativeArea), d.GetID()) // create all collaborative areas
		a.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 (a *collaborativeAreaMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
	return a.StoreOne(data)
}

func filterEnrich[T utils.ShallowDBObject](arr []string, isDrafted bool, a utils.Accessor) []T {
	var new []T
	res, code, _ := a.Search(&dbs.Filters{
		Or: map[string][]dbs.Filter{
			"abstractobject.id": {{Operator: dbs.IN.String(), Value: arr}},
		},
	}, "", isDrafted)
	fmt.Println(res, arr, isDrafted, a)
	if code == 200 {
		for _, r := range res {
			new = append(new, r.(T))
		}
	}
	return new
}

// enrich is a function that enriches the CollaborativeArea with the shared objects
func (a *collaborativeAreaMongoAccessor) enrich(sharedWorkspace *CollaborativeArea, isDrafted bool, request *tools.APIRequest) *CollaborativeArea {
	sharedWorkspace.SharedWorkspaces = filterEnrich[*workspace.Workspace](sharedWorkspace.Workspaces, isDrafted, a.workspaceAccessor)
	sharedWorkspace.SharedWorkflows = filterEnrich[*workflow.Workflow](sharedWorkspace.Workflows, isDrafted, a.workflowAccessor)
	peerskey := []string{}
	fmt.Println("PEERS 1", sharedWorkspace.AllowedPeersGroup)
	for k, v := range sharedWorkspace.AllowedPeersGroup {
		canFound := false
		for _, t := range request.Groups {
			if slices.Contains(v, t) {
				canFound = true
				break
			}
		}
		fmt.Println("PEERS 2", canFound, v)
		if slices.Contains(v, "*") || canFound {
			peerskey = append(peerskey, k)
		}
	}
	fmt.Println("PEERS", peerskey)
	sharedWorkspace.SharedPeers = filterEnrich[*peer.Peer](peerskey, isDrafted, a.peerAccessor)
	sharedWorkspace.SharedRules = filterEnrich[*rule.Rule](sharedWorkspace.Rules, isDrafted, a.ruleAccessor)
	return sharedWorkspace
}

func (a *collaborativeAreaMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
	return utils.GenericLoadOne[*CollaborativeArea](id, func(d utils.DBObject) (utils.DBObject, int, error) {
		return a.enrich(d.(*CollaborativeArea), false, a.Request), 200, nil
	}, a)
}

func (a *collaborativeAreaMongoAccessor) LoadAll(isDrafted bool) ([]utils.ShallowDBObject, int, error) {
	return utils.GenericLoadAll[*CollaborativeArea](func(d utils.DBObject) utils.ShallowDBObject {
		return a.enrich(d.(*CollaborativeArea), isDrafted, a.Request)
	}, isDrafted, a)
}

func (a *collaborativeAreaMongoAccessor) Search(filters *dbs.Filters, search string, isDrafted bool) ([]utils.ShallowDBObject, int, error) {
	return utils.GenericSearch[*CollaborativeArea](filters, search, (&CollaborativeArea{}).GetObjectFilters(search),
		func(d utils.DBObject) utils.ShallowDBObject {
			return a.enrich(d.(*CollaborativeArea), isDrafted, a.Request)
		}, isDrafted, a)
}

/*
sharedWorkspace is a function that shares the collaborative area to the peers
*/
func (a *collaborativeAreaMongoAccessor) sharedWorkspace(shared *CollaborativeArea, id string) {
	eldest, code, _ := a.LoadOne(id) // get the eldest
	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 {
				a.workspaceAccessor.UpdateOne(&workspace.Workspace{Shared: ""}, v)
				if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKSPACE] == nil {
					continue
				}
				paccess := (&peer.Peer{})                 // send to all peers
				for k := range shared.AllowedPeersGroup { // delete the collaborative area on the peer
					b, err := paccess.LaunchPeerExecution(k, v, tools.WORKSPACE, tools.DELETE, nil, a.GetCaller())
					if err != nil && b == nil {
						a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
					}
				}
			}
		}
	}
	if shared.Workspaces != nil {
		for _, v := range shared.Workspaces { // update all the collaborative areas
			workspace, code, _ := a.workspaceAccessor.UpdateOne(&workspace.Workspace{Shared: shared.UUID}, v) // add the shared ref to workspace
			if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKSPACE] == nil {
				continue
			}
			for k := range shared.AllowedPeersGroup {
				if code != 200 {
					continue
				}
				paccess := (&peer.Peer{}) // send to all peers, add the collaborative area on the peer
				s := workspace.Serialize(workspace)
				s["name"] = fmt.Sprintf("%v", s["name"]) + "_" + k
				b, err := paccess.LaunchPeerExecution(k, v, tools.WORKSPACE, tools.POST, s, a.GetCaller())
				if err != nil && b == nil {
					a.Logger.Error().Msg("Could not send to peer " + k + ". 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 (a *collaborativeAreaMongoAccessor) sharedWorkflow(shared *CollaborativeArea, id string) {
	eldest, code, _ := a.LoadOne(id) // get the eldest
	if code == 200 {
		eld := eldest.(*CollaborativeArea)
		if eld.Workflows != nil {
			for _, v := range eld.Workflows {
				data, code, _ := a.workflowAccessor.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
					a.workflowAccessor.UpdateOne(n, v)
					if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKFLOW] == nil {
						continue
					}
					paccess := (&peer.Peer{})                 // send to all peers
					for k := range shared.AllowedPeersGroup { // delete the shared workflow on the peer
						b, err := paccess.LaunchPeerExecution(k, v, tools.WORKFLOW, tools.DELETE, nil, a.GetCaller())
						if err != nil && b == nil {
							a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
						}
					}
				}
			}
		}
	}
	if shared.Workflows != nil { // update all the shared workflows
		for _, v := range shared.Workflows {
			data, code, _ := a.workflowAccessor.LoadOne(v)
			if code == 200 {
				s := data.(*w.Workflow)
				if !slices.Contains(s.Shared, id) {
					s.Shared = append(s.Shared, id)
					workflow, code, _ := a.workflowAccessor.UpdateOne(s, v)
					if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKFLOW] == nil {
						continue
					}
					paccess := (&peer.Peer{})
					for k := range shared.AllowedPeersGroup { // send to all peers
						if code == 200 {
							s := workflow.Serialize(workflow) // add the shared workflow on the peer
							s["name"] = fmt.Sprintf("%v", s["name"]) + "_" + k
							b, err := paccess.LaunchPeerExecution(k, shared.UUID, tools.WORKFLOW, tools.POST, s, a.GetCaller())
							if err != nil && b == nil {
								a.Logger.Error().Msg("Could not send to peer " + k + ". 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 (a *collaborativeAreaMongoAccessor) deleteToPeer(shared *CollaborativeArea) {
	a.contactPeer(shared, tools.POST)
}

// sharedWorkspace is a function that shares the collaborative area to the peers
func (a *collaborativeAreaMongoAccessor) sendToPeer(shared *CollaborativeArea) {
	a.contactPeer(shared, tools.POST)
}

func (a *collaborativeAreaMongoAccessor) contactPeer(shared *CollaborativeArea, meth tools.METHOD) {
	if a.GetCaller() == nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.COLLABORATIVE_AREA] == nil || a.GetCaller().Disabled {
		return
	}

	paccess := (&peer.Peer{})
	for k := range shared.AllowedPeersGroup {
		if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: k}}).IsMySelf(); ok || (shared.IsSent && meth == tools.POST) || (!shared.IsSent && meth != tools.POST) {
			continue
		}
		shared.IsSent = meth == tools.POST
		b, err := paccess.LaunchPeerExecution(k, k, tools.COLLABORATIVE_AREA, meth, shared.Serialize(shared), a.GetCaller())
		if err != nil && b == nil {
			a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
		}
	}
}
