package resources

import (
	"errors"
	"time"

	"cloud.o-forge.io/core/oc-lib/models/booking"
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
	"cloud.o-forge.io/core/oc-lib/tools"
)

type BookingConfiguration struct {
	ExplicitBookingDurationS float64             `json:"explicit_location_duration_s,omitempty" bson:"explicit_location_duration_s,omitempty"`
	UsageStart               *time.Time          `json:"start,omitempty" bson:"start,omitempty"`
	UsageEnd                 *time.Time          `json:"end,omitempty" bson:"end,omitempty"`
	Mode                     booking.BookingMode `json:"mode,omitempty" bson:"mode,omitempty"`
}

type PricedResource[T pricing.PricingProfileITF] struct {
	Name                 string                      `json:"name,omitempty" bson:"name,omitempty"`
	Logo                 string                      `json:"logo,omitempty" bson:"logo,omitempty"`
	InstancesRefs        map[string]string           `json:"instances_refs,omitempty" bson:"instances_refs,omitempty"`
	SelectedPricing      T                           `json:"selected_pricing,omitempty" bson:"selected_pricing,omitempty"`
	Quantity             int                         `json:"quantity,omitempty" bson:"quantity,omitempty"`
	BookingConfiguration *BookingConfiguration       `json:"booking_configuration,omitempty" bson:"booking_configuration,omitempty"`
	Variations           []*pricing.PricingVariation `json:"pricing_variations" bson:"pricing_variations"`
	CreatorID            string                      `json:"peer_id,omitempty" bson:"peer_id,omitempty"`
	ResourceID           string                      `json:"resource_id,omitempty" bson:"resource_id,omitempty"`
	InstanceID           string                      `json:"instance_id,omitempty" bson:"resource_id,omitempty"`

	ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty"`
}

func (abs *PricedResource[T]) GetQuantity() int {
	return abs.Quantity
}

func (abs *PricedResource[T]) AddQuantity(amount int) {
	abs.Quantity += amount
}

func (abs *PricedResource[T]) SelectPricing() pricing.PricingProfileITF {
	return abs.SelectedPricing
}

func (abs *PricedResource[T]) GetID() string {
	return abs.ResourceID
}

func (abs *PricedResource[T]) GetName() string {
	return abs.Name
}

func (abs *PricedResource[T]) GetInstanceID() string {
	return abs.InstanceID
}

func (abs *PricedResource[T]) GetType() tools.DataType {
	return abs.ResourceType
}

func (abs *PricedResource[T]) GetCreatorID() string {
	return abs.CreatorID
}

// IsPurchasable and IsBooked fall back to false when SelectedPricing is a nil interface.
// Concrete types (PricedComputeResource, etc.) override these and guarantee non-nil pricing.
func (abs *PricedResource[T]) IsPurchasable() bool {
	if any(abs.SelectedPricing) == nil {
		return false
	}
	return abs.SelectedPricing.IsPurchasable()
}

func (abs *PricedResource[T]) IsBooked() bool {
	if any(abs.SelectedPricing) == nil {
		return false
	}
	return abs.SelectedPricing.IsBooked()
}

func (abs *PricedResource[T]) GetLocationEnd() *time.Time {
	if abs.BookingConfiguration == nil {
		return nil
	}
	return abs.BookingConfiguration.UsageEnd
}

func (abs *PricedResource[T]) GetLocationStart() *time.Time {
	if abs.BookingConfiguration == nil {
		return nil
	}
	return abs.BookingConfiguration.UsageStart
}

func (abs *PricedResource[T]) SetLocationStart(start time.Time) {
	if abs.BookingConfiguration == nil {
		abs.BookingConfiguration = &BookingConfiguration{}
	}
	abs.BookingConfiguration.UsageStart = &start
}

func (abs *PricedResource[T]) SetLocationEnd(end time.Time) {
	if abs.BookingConfiguration == nil {
		abs.BookingConfiguration = &BookingConfiguration{}
	}
	abs.BookingConfiguration.UsageEnd = &end
}

func (abs *PricedResource[T]) GetBookingMode() booking.BookingMode {
	if abs.BookingConfiguration == nil {
		return booking.WHEN_POSSIBLE
	}
	return abs.BookingConfiguration.Mode
}

func (abs *PricedResource[T]) GetExplicitDurationInS() float64 {
	if abs.BookingConfiguration == nil {
		abs.BookingConfiguration = &BookingConfiguration{}
	}
	if abs.BookingConfiguration.ExplicitBookingDurationS == 0 {
		if abs.BookingConfiguration.UsageEnd == nil && abs.BookingConfiguration.UsageStart == nil {
			return (5 * time.Minute).Seconds()
		}
		if abs.BookingConfiguration.UsageEnd == nil {
			add := abs.BookingConfiguration.UsageStart.Add(5 * time.Minute)
			abs.BookingConfiguration.UsageEnd = &add
		}
		d := abs.BookingConfiguration.UsageEnd.Sub(*abs.BookingConfiguration.UsageStart).Seconds()
		if d <= 0 {
			return (5 * time.Minute).Seconds()
		}
		return d
	}
	return abs.BookingConfiguration.ExplicitBookingDurationS
}

func (r *PricedResource[T]) GetPriceHT() (float64, error) {
	now := time.Now()
	if r.BookingConfiguration == nil {
		r.BookingConfiguration = &BookingConfiguration{}
	}
	if r.BookingConfiguration.UsageStart == nil {
		r.BookingConfiguration.UsageStart = &now
	}
	if r.BookingConfiguration.UsageEnd == nil {
		add := r.BookingConfiguration.UsageStart.Add(time.Duration(5 * time.Minute))
		r.BookingConfiguration.UsageEnd = &add
	}
	if any(r.SelectedPricing) == nil {
		return 0, errors.New("pricing profile must be set for resource " + r.ResourceID)
	}
	pricing := r.SelectedPricing
	return pricing.GetPriceHT(1, 0, *r.BookingConfiguration.UsageStart, *r.BookingConfiguration.UsageEnd, r.Variations)
}
