package workflow

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/resources"
	"cloud.o-forge.io/core/oc-lib/models/utils"
	"cloud.o-forge.io/core/oc-lib/models/workspace"
	"cloud.o-forge.io/core/oc-lib/tools"
)

type workflowMongoAccessor struct {
	utils.AbstractAccessor    // AbstractAccessor contains the basic fields of an accessor (model, caller)
	computeResourceAccessor   utils.Accessor
	collaborativeAreaAccessor utils.Accessor
	workspaceAccessor         utils.Accessor
}

func NewAccessorHistory(request *tools.APIRequest) *workflowMongoAccessor {
	return new(tools.WORKFLOW_HISTORY, request)
}

func NewAccessor(request *tools.APIRequest) *workflowMongoAccessor {
	return new(tools.WORKFLOW, request)
}

// New creates a new instance of the workflowMongoAccessor
func new(t tools.DataType, request *tools.APIRequest) *workflowMongoAccessor {
	return &workflowMongoAccessor{
		computeResourceAccessor:   (&resources.ComputeResource{}).GetAccessor(request),
		collaborativeAreaAccessor: (&shallow_collaborative_area.ShallowCollaborativeArea{}).GetAccessor(request),
		workspaceAccessor:         (&workspace.Workspace{}).GetAccessor(request),
		AbstractAccessor: utils.AbstractAccessor{
			Logger:  logs.CreateLogger(t.String()), // Create a logger with the data type
			Request: request,
			Type:    t,
		},
	}
}

// DeleteOne deletes a workflow from the database, delete depending executions and bookings
func (a *workflowMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
	res, code, err := utils.GenericDeleteOne(id, a)
	if res != nil && code == 200 {
		a.execute(res.(*Workflow), true, false) // up to date the workspace for the workflow
		a.share(res.(*Workflow), true, a.GetCaller())
	}
	return a.verifyResource(res), code, err
}

/*
* share is a function that shares a workflow to the peers if the workflow is shared
 */
func (a *workflowMongoAccessor) share(realData *Workflow, delete bool, caller *tools.HTTPCaller) {
	if realData == nil || realData.Shared == nil || len(realData.Shared) == 0 || caller == nil || caller.Disabled { // no shared no sharing
		return
	}
	for _, sharedID := range realData.Shared { // loop through the shared ids
		res, code, _ := a.collaborativeAreaAccessor.LoadOne(sharedID)
		if code != 200 {
			continue
		}
		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 delete { // if the workflow is deleted, share the deletion	orderResourceAccessor     utils.Accessor

				history := NewHistory()
				history.StoreOne(history.MapFromWorkflow(res.(*Workflow)))
				_, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKFLOW, tools.DELETE,
					map[string]interface{}{}, caller)
			} else { // if the workflow is updated, share the update
				_, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKFLOW, tools.PUT,
					res.Serialize(res), caller)
			}
		}
		if err != nil {
			a.Logger.Error().Msg(err.Error())
		}
	}
}

// UpdateOne updates a workflow in the database
func (a *workflowMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
	// avoid the update if the schedule is the same
	set = a.verifyResource(set)
	if set.(*Workflow).Graph != nil && set.(*Workflow).Graph.Partial {
		return nil, 403, errors.New("you are not allowed to update a partial workflow")
	}
	res, code, err := utils.GenericUpdateOne(a.verifyResource(set), id, a, &Workflow{})
	if code != 200 {
		return nil, code, err
	}
	workflow := res.(*Workflow)
	a.execute(workflow, false, false)       // update the workspace for the workflow
	a.share(workflow, false, a.GetCaller()) // share the update to the peers
	return res, code, nil
}

// StoreOne stores a workflow in the database
func (a *workflowMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
	data = a.verifyResource(data)
	d := data.(*Workflow)
	if d.Graph != nil && d.Graph.Partial {
		return nil, 403, errors.New("you are not allowed to update a partial workflow")
	}
	res, code, err := utils.GenericStoreOne(d, a)
	if err != nil || code != 200 {
		return nil, code, err
	}
	workflow := res.(*Workflow)

	a.share(workflow, false, a.GetCaller()) // share the creation to the peers
	a.execute(workflow, false, false)       // store the workspace for the workflow
	return res, code, nil
}

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

// execute is a function that executes a workflow
// it stores the workflow resources in a specific workspace to never have a conflict in UI and logic
func (a *workflowMongoAccessor) execute(workflow *Workflow, delete bool, active bool) {
	filters := &dbs.Filters{
		Or: map[string][]dbs.Filter{ // filter by standard workspace name attached to a workflow
			"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: workflow.Name + "_workspace"}},
		},
	}
	resource, _, err := a.workspaceAccessor.Search(filters, "", workflow.IsDraft)
	if delete { // if delete is set to true, delete the workspace
		for _, r := range resource {
			a.workspaceAccessor.DeleteOne(r.GetID())
		}
		return
	}
	if err == nil && len(resource) > 0 { // if the workspace already exists, update it
		a.workspaceAccessor.UpdateOne(&workspace.Workspace{
			Active: active,
			ResourceSet: resources.ResourceSet{
				Datas:       workflow.Datas,
				Processings: workflow.Processings,
				Storages:    workflow.Storages,
				Workflows:   workflow.Workflows,
				Computes:    workflow.Computes,
			},
		}, resource[0].GetID())
	} else { // if the workspace does not exist, create it
		a.workspaceAccessor.StoreOne(&workspace.Workspace{
			Active:         active,
			AbstractObject: utils.AbstractObject{Name: workflow.Name + "_workspace"},
			ResourceSet: resources.ResourceSet{
				Datas:       workflow.Datas,
				Processings: workflow.Processings,
				Storages:    workflow.Storages,
				Workflows:   workflow.Workflows,
				Computes:    workflow.Computes,
			},
		})
	}
}

func (a *workflowMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
	return utils.GenericLoadOne[*Workflow](id, func(d utils.DBObject) (utils.DBObject, int, error) {
		w := d.(*Workflow)
		a.execute(w, false, true) // if no workspace is attached to the workflow, create it
		return d, 200, nil
	}, a)
}

func (a *workflowMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
	return utils.GenericLoadAll[*Workflow](func(d utils.DBObject) utils.ShallowDBObject { return &d.(*Workflow).AbstractObject }, isDraft, a)
}

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

func (a *workflowMongoAccessor) verifyResource(obj utils.DBObject) utils.DBObject {
	wf := obj.(*Workflow)
	if wf.Graph == nil {
		return wf
	}
	for _, item := range wf.Graph.Items {
		t, resource := item.GetResource()
		if resource == nil {
			continue
		}
		var access utils.Accessor
		if t == tools.COMPUTE_RESOURCE {
			access = resources.NewAccessor[*resources.ComputeResource](t, a.GetRequest(), func() utils.DBObject { return &resources.ComputeResource{} })
		} else if t == tools.PROCESSING_RESOURCE {
			access = resources.NewAccessor[*resources.ProcessingResource](t, a.GetRequest(), func() utils.DBObject { return &resources.ProcessingResource{} })
		} else if t == tools.STORAGE_RESOURCE {
			access = resources.NewAccessor[*resources.StorageResource](t, a.GetRequest(), func() utils.DBObject { return &resources.StorageResource{} })
		} else if t == tools.WORKFLOW_RESOURCE {
			access = resources.NewAccessor[*resources.WorkflowResource](t, a.GetRequest(), func() utils.DBObject { return &resources.WorkflowResource{} })
		} else if t == tools.DATA_RESOURCE {
			access = resources.NewAccessor[*resources.DataResource](t, a.GetRequest(), func() utils.DBObject { return &resources.DataResource{} })
		} else {
			wf.Graph.Clear(item.Data.GetID())
		}
		if error := utils.VerifyAccess(access, resource.GetID()); error != nil {
			wf.Graph.Clear(item.Data.GetID())
		}
	}
	return wf
}
