package workspace

import (
	"errors"

	"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/shallow_collaborative_area"
	"cloud.o-forge.io/core/oc-lib/models/peer"
	"cloud.o-forge.io/core/oc-lib/models/resources/data"
	"cloud.o-forge.io/core/oc-lib/models/resources/datacenter"
	"cloud.o-forge.io/core/oc-lib/models/resources/processing"
	"cloud.o-forge.io/core/oc-lib/models/resources/storage"
	w "cloud.o-forge.io/core/oc-lib/models/resources/workflow"
	"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 New() *workspaceMongoAccessor {
	return &workspaceMongoAccessor{}
}

// 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 (wfa *workspaceMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
	res, code, err := wfa.GenericDeleteOne(id, wfa)
	if code == 200 && res != nil {
		wfa.share(res.(*Workspace), tools.DELETE, wfa.Caller) // 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 (wfa *workspaceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
	d := set.(*Workspace) // Get the workspace from the set
	d.DataResources = nil // Reset the resources
	d.DatacenterResources = nil
	d.StorageResources = nil
	d.ProcessingResources = nil
	d.WorkflowResources = nil
	if d.Active { // If the workspace is active, deactivate all the other workspaces
		res, _, err := wfa.LoadAll()
		if err == nil {
			for _, r := range res {
				if r.GetID() != id {
					r.(*Workspace).Active = false
					wfa.UpdateOne(r.(*Workspace), r.GetID())
				}
			}
		}
	}
	res, code, err := wfa.GenericUpdateOne(set, id, wfa, &Workspace{})
	if code == 200 && res != nil {
		wfa.share(res.(*Workspace), tools.PUT, wfa.Caller)
	}
	return res, code, err
}

// StoreOne stores a workspace in the database, it checks if a workspace with the same name already exists
func (wfa *workspaceMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
	filters := &dbs.Filters{
		Or: map[string][]dbs.Filter{
			"abstractobject.name": {{dbs.LIKE.String(), data.GetName() + "_workspace"}},
		},
	}
	res, _, err := wfa.Search(filters, "") // 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.DataResources = nil
	d.DatacenterResources = nil
	d.StorageResources = nil
	d.ProcessingResources = nil
	d.WorkflowResources = nil
	return wfa.GenericStoreOne(d, wfa)
}

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

/*
This function is used to fill the workspace with the resources
*/
func (wfa *workspaceMongoAccessor) fill(workflow *Workspace) *Workspace {
	// Fill the workspace with the resources
	if workflow.Datas != nil && len(workflow.Datas) > 0 {
		dataAccessor := (&data.DataResource{}).GetAccessor(nil)
		for _, id := range workflow.Datas {
			d, _, e := dataAccessor.LoadOne(id)
			if e == nil {
				workflow.DataResources = append(workflow.DataResources, d.(*data.DataResource))
			}
		}
	}
	// Fill the workspace with the datacenters
	if workflow.Datacenters != nil && len(workflow.Datacenters) > 0 {
		dataAccessor := (&datacenter.DatacenterResource{}).GetAccessor(nil)
		for _, id := range workflow.Datacenters {
			d, _, e := dataAccessor.LoadOne(id)
			if e == nil {
				workflow.DatacenterResources = append(workflow.DatacenterResources, d.(*datacenter.DatacenterResource))
			}
		}
	}
	// Fill the workspace with the storages
	if workflow.Storages != nil && len(workflow.Storages) > 0 {
		dataAccessor := (&storage.StorageResource{}).GetAccessor(nil)
		for _, id := range workflow.Storages {
			d, _, e := dataAccessor.LoadOne(id)
			if e == nil {
				workflow.StorageResources = append(workflow.StorageResources, d.(*storage.StorageResource))
			}
		}
	}
	// Fill the workspace with the processings
	if workflow.Processings != nil && len(workflow.Processings) > 0 {
		dataAccessor := (&processing.ProcessingResource{}).GetAccessor(nil)
		for _, id := range workflow.Processings {
			d, _, e := dataAccessor.LoadOne(id)
			if e == nil {
				workflow.ProcessingResources = append(workflow.ProcessingResources, d.(*processing.ProcessingResource))
			}
		}
	}
	// Fill the workspace with the workflows
	if workflow.Workflows != nil && len(workflow.Workflows) > 0 {
		dataAccessor := (&w.WorkflowResource{}).GetAccessor(nil)
		for _, id := range workflow.Workflows {
			d, _, e := dataAccessor.LoadOne(id)
			if e == nil {
				workflow.WorkflowResources = append(workflow.WorkflowResources, d.(*w.WorkflowResource))
			}
		}
	}
	return workflow
}

// LoadOne loads a workspace from the database, given its ID
func (wfa *workspaceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
	var workflow Workspace
	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(&workflow)

	return wfa.fill(&workflow), 200, nil
}

// LoadAll loads all the workspaces from the database
func (wfa workspaceMongoAccessor) 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 []Workspace
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
		return nil, 404, err
	}
	for _, r := range results {
		objs = append(objs, wfa.fill(&r))
	}
	return objs, 200, nil
}

// Search searches for workspaces in the database, given some filters OR a search string
func (wfa *workspaceMongoAccessor) 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{ // filter by name if no filters are provided
				"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 []Workspace
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
		return nil, 404, err
	}
	for _, r := range results {
		objs = append(objs, wfa.fill(&r))
	}
	return objs, 200, nil
}

/*
This function is used to share the workspace with the peers
*/
func (wfa *workspaceMongoAccessor) share(realData *Workspace, method tools.METHOD, caller *tools.HTTPCaller) {
	if realData.Shared == "" || caller.Disabled {
		return
	}
	access := (&shallow_collaborative_area.ShallowCollaborativeArea{}).GetAccessor(nil)
	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 paccess.IsMySelf() { // 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(), caller)
		}
	}
	if err != nil {
		wfa.Logger.Error().Msg(err.Error())
	}
}
