package booking

import (
	"time"

	"cloud.o-forge.io/core/oc-lib/models/common/enum"
	"cloud.o-forge.io/core/oc-lib/models/common/models"
	"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"
)


/*
* Booking is a struct that represents a booking
 */
type Booking struct {
	utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)

	FromNano   string                 `json:"from_nano,omitempty" bson:"priced_item,omitempty"`
	PricedItem map[string]interface{} `json:"priced_item,omitempty" bson:"priced_item,omitempty"` // We need to add the validate:"required" tag once the pricing feature is implemented, removed to avoid handling the error

	ResumeMetrics    map[string]map[string]models.MetricResume `json:"resume_metrics,omitempty" bson:"resume_metrics,omitempty"`
	ExecutionMetrics map[string][]models.MetricsSnapshot       `json:"metrics,omitempty" bson:"metrics,omitempty"`

	ExecutionsID      string             `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions
	DestPeerID        string             `json:"dest_peer_id,omitempty" bson:"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" bson:"state"`                                                                     // 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
	InstanceID   string         `json:"instance_id,omitempty" bson:"instance_id,omitempty" validate:"required"`     // could be a Compute or a Storage

	// Authorization: identifies who created this draft and the Check session it belongs to.
	// Used to verify UPDATE and DELETE orders from remote schedulers.
	SchedulerPeerID string `json:"scheduler_peer_id,omitempty" bson:"scheduler_peer_id,omitempty"`

	// Peerless is true when the booked resource has no destination peer
	// (e.g. a public Docker Hub image). No peer confirmation or pricing
	// negotiation is needed; the booking is stored locally only.
	Peerless bool `json:"peerless,omitempty" bson:"peerless,omitempty"`

	// OriginRef carries the registry reference of a peerless resource
	// (e.g. "docker.io/pytorch/pytorch:2.1") so schedulers can validate it.
	OriginRef string `json:"origin_ref,omitempty" bson:"origin_ref,omitempty"`

	// BillingStrategy est la fréquence de facturation appliquée à ce booking
	// (BILL_ONCE, BILL_PER_WEEK, BILL_PER_MONTH, BILL_PER_YEAR).
	// Transmis depuis WorkflowExecution.SelectedBillingStrategy lors du Book().
	BillingStrategy pricing.BillingStrategy `json:"billing_strategy" bson:"billing_strategy"`

	// PaymentType est le mode de paiement choisi pour cette ressource spécifique
	// (PAY_ONCE, PAY_EVERY_WEEK, PAY_EVERY_MONTH, PAY_EVERY_YEAR).
	// Résolu depuis WorkflowExecution.SelectedPaymentMode[itemID] lors du Book().
	PaymentType pricing.PaymentType `json:"payment_type" bson:"payment_type"`
}

func (b *Booking) CalcDeltaOfExecution() map[string]map[string]models.MetricResume {
	m := map[string]map[string]models.MetricResume{}
	for instance, snapshot := range b.ExecutionMetrics {
		m[instance] = map[string]models.MetricResume{}
		for _, metric := range snapshot {
			for _, mm := range metric.Metrics {
				if resume, ok := m[instance][mm.Name]; !ok {
					m[instance][mm.Name] = models.MetricResume{
						Delta:     0,
						LastValue: mm.Value,
					}
				} else {
					delta := resume.LastValue - mm.Value
					if delta == 0 {
						resume.Delta = delta
					} else {
						resume.Delta = (resume.Delta + delta) / 2
					}
					resume.LastValue = mm.Value
					m[instance][mm.Name] = resume
				}
			}
		}
	}
	return m
}

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

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

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) GetAccessor(request *tools.APIRequest) utils.Accessor {
	return NewAccessor(request) // Create a new instance of the accessor
}

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

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

func (r *Booking) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
	incoming := set.(*Booking)
	if !r.IsDraft && r.State != incoming.State || r.RealStartDate != incoming.RealStartDate || r.RealEndDate != incoming.RealEndDate {
		patch := &Booking{
			State:         incoming.State,
			RealStartDate: incoming.RealStartDate,
			RealEndDate:   incoming.RealEndDate,
		}
		// Auto-set RealStartDate when transitioning to STARTED and not already set
		if r.State != enum.STARTED && incoming.State == enum.STARTED && patch.RealStartDate == nil {
			now := time.Now()
			patch.RealStartDate = &now
		}
		return true, patch
	}
	return r.IsDraft, set
}

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