package resources

import (
	"errors"
	"fmt"
	"strings"
	"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/live"
	"cloud.o-forge.io/core/oc-lib/models/utils"
	"cloud.o-forge.io/core/oc-lib/tools"
	"github.com/google/uuid"
)

/*
* ComputeResource is a struct that represents a compute resource
* it defines the resource compute
 */
type ComputeResource struct {
	AbstractInstanciatedResource[*ComputeResourceInstance]
	Architecture   string                  `json:"architecture,omitempty" bson:"architecture,omitempty"` // Architecture is the architecture
	Infrastructure enum.InfrastructureType `json:"infrastructure" bson:"infrastructure" default:"-1"`    // Infrastructure is the infrastructure
}

func (d *ComputeResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
	return NewAccessor[*ComputeResource](tools.COMPUTE_RESOURCE, request)
}

func (r *ComputeResource) GetType() string {
	return tools.COMPUTE_RESOURCE.String()
}

func (abs *ComputeResource) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) {
	if t != tools.COMPUTE_RESOURCE {
		return nil, errors.New("not the proper type expected : cannot convert to priced resource : have " + t.String() + " wait Compute")
	}
	p, err := ConvertToPricedResource[*ComputeResourcePricingProfile](t, selectedInstance, selectedPartnership, selectedBuyingStrategy, selectedStrategy, selectedBookingModeIndex, abs, request)
	if err != nil {
		return nil, err
	}
	priced := p.(*PricedResource[*ComputeResourcePricingProfile])
	return &PricedComputeResource{
		PricedResource: *priced,
	}, nil
}

type ComputeResourceInstance struct {
	ResourceInstance[*ComputeResourcePartnership]
	Source             string                 `json:"source,omitempty" bson:"source,omitempty"`
	SecurityLevel      string                 `json:"security_level,omitempty" bson:"security_level,omitempty"`
	PowerSources       []string               `json:"power_sources,omitempty" bson:"power_sources,omitempty"`
	AnnualCO2Emissions float64                `json:"annual_co2_emissions,omitempty" bson:"co2_emissions,omitempty"`
	CPUs               map[string]*models.CPU `bson:"cpus,omitempty" json:"cpus,omitempty"`
	GPUs               map[string]*models.GPU `bson:"gpus,omitempty" json:"gpus,omitempty"`
	Nodes              []*live.ComputeNode    `json:"nodes,omitempty" bson:"nodes,omitempty"`
	// AvailableStorages lists storage capabilities activatable on this compute unit (e.g. Minio, local volumes).
	// These are shallow StorageResource entries — not independent catalog items — but carry full pricing structure.
	AvailableStorages []*StorageResource `json:"available_storages,omitempty" bson:"available_storages,omitempty"`
}

// IsPeerless is always false for compute instances: a compute resource is
// infrastructure owned by a peer and can never be declared peerless.
func (ri *ComputeResourceInstance) IsPeerless() bool { return false }

func NewComputeResourceInstance(name string, peerID string) ResourceInstanceITF {
	return &ComputeResourceInstance{
		ResourceInstance: ResourceInstance[*ComputeResourcePartnership]{
			AbstractObject: utils.AbstractObject{
				UUID: uuid.New().String(),
				Name: name,
			},
		},
	}
}

type ComputeResourcePartnership struct {
	ResourcePartnerShip[*ComputeResourcePricingProfile]
	MinGaranteedCPUsCores    map[string]float64 `json:"garanteed_cpus,omitempty" bson:"garanteed_cpus,omitempty"`
	MinGaranteedGPUsMemoryGB map[string]float64 `json:"garanteed_gpus,omitempty" bson:"garanteed_gpus,omitempty"`
	MinGaranteedRAMSize      float64            `json:"garanteed_ram,omitempty" bson:"garanteed_ram,omitempty"`

	MaxAllowedCPUsCores    map[string]float64 `json:"allowed_cpus,omitempty" bson:"allowed_cpus,omitempty"`
	MaxAllowedGPUsMemoryGB map[string]float64 `json:"allowed_gpus,omitempty" bson:"allowed_gpus,omitempty"`
	MaxAllowedRAMSize      float64            `json:"allowed_ram,omitempty" bson:"allowed_ram,omitempty"`
}

type ComputeResourcePricingProfile struct {
	pricing.ExploitPricingProfile[pricing.TimePricingStrategy]
	// ExploitPricingProfile is the pricing profile of a compute it means that we exploit the resource for an amount of continuous time
	CPUsPrices map[string]float64 `json:"cpus_prices,omitempty" bson:"cpus_prices,omitempty"` // CPUsPrices is the prices of the CPUs
	GPUsPrices map[string]float64 `json:"gpus_prices,omitempty" bson:"gpus_prices,omitempty"` // GPUsPrices is the prices of the GPUs
	RAMPrice   float64            `json:"ram_price" bson:"ram_price" default:"-1"`            // RAMPrice is the price of the RAM
}

func (p *ComputeResourcePricingProfile) IsPurchasable() bool {
	fmt.Println("Buying", p.Pricing.BuyingStrategy)
	return p.Pricing.BuyingStrategy == pricing.PERMANENT
}

func (p *ComputeResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
	return p.Pricing.BuyingStrategy
}

func (p *ComputeResourcePricingProfile) IsBooked() bool {
	if p.Pricing.BuyingStrategy == pricing.PERMANENT {
		p.Pricing.BuyingStrategy = pricing.SUBSCRIPTION
	}
	return true
}

func (p *ComputeResourcePricingProfile) GetOverrideStrategyValue() int {
	return -1
}

// NOT A PROPER QUANTITY
// amountOfData is the number of CPUs, GPUs or RAM dependings on the params
func (p *ComputeResourcePricingProfile) GetPriceHT(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, variation []*pricing.PricingVariation, params ...string) (float64, error) {
	if len(params) < 1 {
		return 0, errors.New("params must be set")
	}
	pp := float64(0)
	model := ""
	if len(params) > 1 {
		model = params[1]
	}
	if strings.Contains(params[0], "cpus") && len(params) > 1 {
		if _, ok := p.CPUsPrices[model]; ok {
			p.Pricing.Price = p.CPUsPrices[model]
		}
		r, err := p.Pricing.GetPriceHT(amountOfData, explicitDuration, start, &end, variation)
		if err != nil {
			return 0, err
		}
		pp += r

	}
	if strings.Contains(params[0], "gpus") && len(params) > 1 {
		if _, ok := p.GPUsPrices[model]; ok {
			p.Pricing.Price = p.GPUsPrices[model]
		}
		r, err := p.Pricing.GetPriceHT(amountOfData, explicitDuration, start, &end, variation)
		if err != nil {
			return 0, err
		}
		pp += r
	}
	if strings.Contains(params[0], "ram") {
		if p.RAMPrice >= 0 {
			p.Pricing.Price = p.RAMPrice
		}
		r, err := p.Pricing.GetPriceHT(float64(amountOfData), explicitDuration, start, &end, variation)
		if err != nil {
			return 0, err
		}
		pp += r
	}
	return pp, nil
}

type PricedComputeResource struct {
	PricedResource[*ComputeResourcePricingProfile]

	CPUsLocated map[string]float64 `json:"cpus_in_use" bson:"cpus_in_use"` // CPUsInUse is the list of CPUs in use
	GPUsLocated map[string]float64 `json:"gpus_in_use" bson:"gpus_in_use"` // GPUsInUse is the list of GPUs in use
	RAMLocated  float64            `json:"ram_in_use" bson:"ram_in_use"`   // RAMInUse is the RAM in use
}

func (r *PricedComputeResource) ensurePricing() {
	if r.SelectedPricing == nil {
		r.SelectedPricing = &ComputeResourcePricingProfile{}
	}
}

func (r *PricedComputeResource) IsPurchasable() bool {
	r.ensurePricing()
	return r.SelectedPricing.IsPurchasable()
}

func (r *PricedComputeResource) IsBooked() bool {
	r.ensurePricing()
	return r.SelectedPricing.IsBooked()
}

func (r *PricedComputeResource) GetType() tools.DataType {
	return tools.COMPUTE_RESOURCE
}

func (r *PricedComputeResource) GetPriceHT() (float64, error) {
	r.ensurePricing()
	if r.BookingConfiguration == nil {
		r.BookingConfiguration = &BookingConfiguration{}
	}
	now := time.Now()
	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
	}
	pricing := r.SelectedPricing
	price := float64(0)
	for _, l := range []map[string]float64{r.CPUsLocated, r.GPUsLocated} {
		for model, amountOfData := range l {
			cpus, err := pricing.GetPriceHT(float64(amountOfData),
				r.BookingConfiguration.ExplicitBookingDurationS, *r.BookingConfiguration.UsageStart,
				*r.BookingConfiguration.UsageEnd, r.Variations, "cpus", model)
			if err != nil {
				return 0, err
			}
			price += cpus
		}
	}
	ram, err := pricing.GetPriceHT(r.RAMLocated, r.BookingConfiguration.ExplicitBookingDurationS,
		*r.BookingConfiguration.UsageStart, *r.BookingConfiguration.UsageEnd, r.Variations, "ram")
	if err != nil {
		return 0, err
	}
	price += ram
	return price, nil
}

/*
* FillWithDefaultProcessingUsage fills the order item with the default processing usage
* it depends on the processing usage only if nothing is set, during order
 */
func (i *PricedComputeResource) FillWithDefaultProcessingUsage(usage *ProcessingUsage) {
	for _, cpu := range usage.CPUs {
		if _, ok := i.CPUsLocated[cpu.Model]; !ok {
			i.CPUsLocated[cpu.Model] = 0
		}
		if i.CPUsLocated[cpu.Model] < float64(cpu.Cores) {
			i.CPUsLocated[cpu.Model] = float64(cpu.Cores)
		}
	}
	for _, cpu := range usage.GPUs {
		i.GPUsLocated[cpu.Model] = 1
	}
	i.RAMLocated = usage.RAM.SizeGb
}
