package resources

import (
	"errors"
	"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/utils"
	"cloud.o-forge.io/core/oc-lib/tools"
)

/*
* ComputeResource is a struct that represents a compute resource
* it defines the resource compute
 */
type ComputeResource struct {
	AbstractIntanciatedResource[*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() utils.DBObject { return &ComputeResource{} })
}

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

func (abs *ComputeResource) ConvertToPricedResource(
	t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
	if t != tools.COMPUTE_RESOURCE {
		return nil
	}
	p := abs.AbstractIntanciatedResource.ConvertToPricedResource(t, request)
	priced := p.(*PricedResource)
	return &PricedComputeResource{
		PricedResource: *priced,
	}
}

type ComputeNode struct {
	Name     string           `json:"name,omitempty" bson:"name,omitempty"`
	Quantity int64            `json:"quantity" bson:"quantity" default:"1"`
	RAM      *models.RAM      `bson:"ram,omitempty" json:"ram,omitempty"`   // RAM is the RAM
	CPUs     map[string]int64 `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model
	GPUs     map[string]int64 `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model
}

type ComputeResourceInstance struct {
	ResourceInstance[*ComputeResourcePartnership]
	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"` // CPUs is the list of CPUs key is model
	GPUs               map[string]*models.GPU `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model
	Nodes              []*ComputeNode         `json:"nodes,omitempty" bson:"nodes,omitempty"`
}

type ComputeResourcePartnership struct {
	ResourcePartnerShip[*ComputeResourcePricingProfile]
	MaxAllowedCPUsCores    map[string]int     `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) IsPurchased() bool {
	return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE
}

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) GetPrice(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, params ...string) (float64, error) {
	if len(params) < 1 {
		return 0, errors.New("params must be set")
	}
	pp := float64(0)
	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.GetPrice(amountOfData, explicitDuration, start, &end)
		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.GetPrice(amountOfData, explicitDuration, start, &end)
		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.GetPrice(float64(amountOfData), explicitDuration, start, &end)
		if err != nil {
			return 0, err
		}
		pp += r
	}
	return pp, nil
}

type PricedComputeResource struct {
	PricedResource

	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) GetType() tools.DataType {
	return tools.COMPUTE_RESOURCE
}

func (r *PricedComputeResource) GetPrice() (float64, error) {
	if r.UsageStart == nil || r.UsageEnd == nil {
		return 0, errors.New("usage start and end must be set")
	}
	if r.SelectedPricing == nil {
		return 0, errors.New("selected pricing must be set")
	}
	pricing := *r.SelectedPricing
	price := float64(0)
	for _, l := range []map[string]float64{r.CPUsLocated, r.GPUsLocated} {
		for model, amountOfData := range l {
			cpus, err := pricing.GetPrice(float64(amountOfData), r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, "cpus", model)
			if err != nil {
				return 0, err
			}
			price += cpus
		}
	}
	ram, err := pricing.GetPrice(r.RAMLocated, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, "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
}
