package booking

import (
	"time"

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

/*
* Booking is a struct that represents a booking
 */
type Booking struct {
	utils.AbstractObject                    // AbstractObject contains the basic fields of an object (id, name)
	DestPeerID           string             `json:"dest_peer_id,omitempty"`                             // DestPeerID is the ID of the destination peer
	WorkflowID           string             `json:"workflow_id,omitempty" bson:"workflow_id,omitempty"` // WorkflowID is the ID of the workflow
	ExecutionID          string             `json:"execution_id,omitempty" bson:"execution_id,omitempty" validate:"required"`
	State                enum.BookingStatus `json:"state,omitempty" bson:"state,omitempty" validate:"required"`                             // State is the state of the booking
	ExpectedStartDate    time.Time          `json:"expected_start_date,omitempty" bson:"expected_start_date,omitempty" validate:"required"` // ExpectedStartDate is the expected start date of the booking
	ExpectedEndDate      *time.Time         `json:"expected_end_date,omitempty" bson:"expected_end_date,omitempty" validate:"required"`     // ExpectedEndDate is the expected end date of the booking

	RealStartDate *time.Time `json:"real_start_date,omitempty" bson:"real_start_date,omitempty"` // RealStartDate is the real start date of the booking
	RealEndDate   *time.Time `json:"real_end_date,omitempty" bson:"real_end_date,omitempty"`     // RealEndDate is the real end date of the booking

	ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty" validate:"required"` // ResourceType is the type of the resource
	ResourceID   string         `json:"resource_id,omitempty" bson:"resource_id,omitempty" validate:"required"`     // could be a Compute or a Storage
}

// CheckBooking checks if a booking is possible on a specific compute resource
func (wfa *Booking) Check(id string, start time.Time, end *time.Time, parrallelAllowed int) (bool, error) {
	// check if
	if end == nil {
		// if no end... then Book like a savage
		e := start.Add(time.Hour)
		end = &e
	}
	accessor := NewAccessor(nil)
	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
			"resource_id": {{Operator: dbs.EQUAL.String(), Value: id}},
			"state":       {{Operator: dbs.EQUAL.String(), Value: enum.DRAFT.EnumIndex()}},
			"expected_start_date": {
				{Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(*end)},
				{Operator: dbs.GTE.String(), Value: primitive.NewDateTimeFromTime(start)},
			},
		},
	}, "", wfa.IsDraft)
	if code != 200 {
		return false, err
	}
	return len(res) <= parrallelAllowed, nil
}

func (d *Booking) GetDelayForLaunch() time.Duration {
	return d.RealStartDate.Sub(d.ExpectedStartDate)
}

func (d *Booking) GetDelayForFinishing() time.Duration {
	if d.ExpectedEndDate == nil {
		return time.Duration(0)
	}
	return d.RealEndDate.Sub(d.ExpectedStartDate)
}

func (d *Booking) GetUsualDuration() time.Duration {
	return d.ExpectedEndDate.Sub(d.ExpectedStartDate)
}

func (d *Booking) GetRealDuration() time.Duration {
	if d.RealEndDate == nil || d.RealStartDate == nil {
		return time.Duration(0)
	}
	return d.RealEndDate.Sub(*d.RealStartDate)
}

func (d *Booking) GetDelayOnDuration() time.Duration {
	return d.GetRealDuration() - d.GetUsualDuration()
}

func (d *Booking) GetName() string {
	return d.GetID() + "_" + d.ExpectedStartDate.String()
}

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

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

func (r *Booking) StoreDraftDefault() {
	r.IsDraft = true
}

func (r *Booking) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
	if !r.IsDraft && r.State != set.(*Booking).State || r.RealStartDate != set.(*Booking).RealStartDate || r.RealEndDate != set.(*Booking).RealEndDate {
		return true, &Booking{
			State:         set.(*Booking).State,
			RealStartDate: set.(*Booking).RealStartDate,
			RealEndDate:   set.(*Booking).RealEndDate,
		} // only state can be updated
	}
	// TODO : HERE WE CAN HANDLE THE CASE WHERE THE BOOKING IS DELAYED OR EXCEEDING OR ending sooner
	return r.IsDraft, set
}

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