package workflow_execution

import (
	"strings"
	"time"

	"cloud.o-forge.io/core/oc-lib/dbs"
	"cloud.o-forge.io/core/oc-lib/models/booking"
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
	"cloud.o-forge.io/core/oc-lib/models/utils"
	"cloud.o-forge.io/core/oc-lib/tools"
	"github.com/google/uuid"
	"go.mongodb.org/mongo-driver/bson/primitive"
)

/*
* WorkflowExecutions is a struct that represents a list of workflow executions
* Warning: No user can write (del, post, put) a workflow execution, it is only used by the system
* workflows generate their own executions
 */
type WorkflowExecutions struct {
	utils.AbstractObject                    // AbstractObject contains the basic fields of an object (id, name)
	ExecDate             time.Time          `json:"execution_date,omitempty" bson:"execution_date,omitempty" validate:"required"` // ExecDate is the execution date of the workflow, is required
	EndDate              *time.Time         `json:"end_date,omitempty" bson:"end_date,omitempty"`                                 // EndDate is the end date of the workflow
	State                enum.BookingStatus `json:"state" bson:"state" default:"0"`                                               // TEMPORARY TODO DEFAULT 1 -> 0 State is the state of the workflow
	WorkflowID           string             `json:"workflow_id" bson:"workflow_id,omitempty"`                                     // WorkflowID is the ID of the workflow
}

func (r *WorkflowExecutions) StoreDraftDefault() {
	r.IsDraft = true // TODO: TEMPORARY
	r.State = enum.DRAFT
}

func (r *WorkflowExecutions) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
	if r.State != set.(*WorkflowExecutions).State {
		return true, &WorkflowExecutions{State: set.(*WorkflowExecutions).State} // only state can be updated
	}
	return !r.IsDraft, set // only draft buying can be updated
}

func (r *WorkflowExecutions) CanDelete() bool {
	return r.IsDraft // only draft bookings can be deleted
}

func (wfa *WorkflowExecutions) Equals(we *WorkflowExecutions) bool {
	return wfa.ExecDate.Equal(we.ExecDate) && wfa.WorkflowID == we.WorkflowID
}

func (ws *WorkflowExecutions) PurgeDraft(request *tools.APIRequest) error {
	if ws.EndDate == nil {
		// if no end... then Book like a savage
		e := ws.ExecDate.Add(time.Hour)
		ws.EndDate = &e
	}
	accessor := ws.GetAccessor(request)
	res, code, err := accessor.Search(&dbs.Filters{
		And: map[string][]dbs.Filter{ // check if there is a booking on the same compute resource by filtering on the compute_resource_id, the state and the execution date
			"state":       {{Operator: dbs.EQUAL.String(), Value: enum.DRAFT.EnumIndex()}},
			"workflow_id": {{Operator: dbs.EQUAL.String(), Value: ws.WorkflowID}},
			"execution_date": {
				{Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(*ws.EndDate)},
				{Operator: dbs.GTE.String(), Value: primitive.NewDateTimeFromTime(ws.ExecDate)},
			},
		},
	}, "", ws.IsDraft)
	if code != 200 || err != nil {
		return err
	}
	for _, r := range res {
		accessor.DeleteOne(r.GetID())
	}
	return nil
}

// tool to transform the argo status to a state
func (wfa *WorkflowExecutions) ArgoStatusToState(status string) *WorkflowExecutions {
	status = strings.ToLower(status)
	switch status {
	case "succeeded": // Succeeded
		wfa.State = enum.SUCCESS
	case "pending": // Pending
		wfa.State = enum.SCHEDULED
	case "running": // Running
		wfa.State = enum.STARTED
	default: // Failed
		wfa.State = enum.FAILURE
	}
	return wfa
}

func (r *WorkflowExecutions) GenerateID() {
	r.UUID = uuid.New().String()
}

func (d *WorkflowExecutions) GetName() string {
	return d.UUID + "_" + d.ExecDate.String()
}

func (d *WorkflowExecutions) GetAccessor(request *tools.APIRequest) utils.Accessor {
	return NewAccessor(request) // Create a new instance of the accessor
}

func (d *WorkflowExecutions) VerifyAuth(request *tools.APIRequest) bool {
	return true
}

func (d *WorkflowExecutions) Book(wfID string, priceds map[tools.DataType][]pricing.PricedItemITF) []*booking.Booking {
	booking := d.bookEach(wfID, tools.STORAGE_RESOURCE, priceds[tools.STORAGE_RESOURCE])
	booking = append(booking, d.bookEach(wfID, tools.PROCESSING_RESOURCE, priceds[tools.PROCESSING_RESOURCE])...)
	return booking
}

func (d *WorkflowExecutions) bookEach(wfID string, dt tools.DataType, priceds []pricing.PricedItemITF) []*booking.Booking {
	items := []*booking.Booking{}
	for _, priced := range priceds {
		start := d.ExecDate
		if s := priced.GetLocationStart(); s != nil {
			start = *s
		}
		end := start.Add(time.Duration(priced.GetExplicitDurationInS()) * time.Second)
		bookingItem := &booking.Booking{
			State:             enum.DRAFT,
			ResourceID:        priced.GetID(),
			ResourceType:      dt,
			DestPeerID:        priced.GetCreatorID(),
			WorkflowID:        wfID,
			ExecutionID:       d.GetID(),
			ExpectedStartDate: start,
			ExpectedEndDate:   &end,
		}
		items = append(items, bookingItem)
	}
	return items
}
