package resources

import (
	"errors"
	"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"
)

type ServiceMode int

const (
	DEPLOYMENT ServiceMode = iota // deploy the service, pay for uptime — duration unbounded
	HOSTED                        // use an existing service, pay per call — duration per request
)

func (m ServiceMode) String() string {
	return [...]string{"DEPLOYMENT", "HOSTED"}[m]
}

type ServiceUsage struct {
	CPUs         map[string]*models.CPU `bson:"cpus,omitempty" json:"cpus,omitempty"`
	GPUs         map[string]*models.GPU `bson:"gpus,omitempty" json:"gpus,omitempty"`
	RAM          *models.RAM            `bson:"ram,omitempty" json:"ram,omitempty"`
	StorageGb    float64                `bson:"storage,omitempty" json:"storage,omitempty"`
	Hypothesis   string                 `bson:"hypothesis,omitempty" json:"hypothesis,omitempty"`
	ScalingModel string                 `bson:"scaling_model,omitempty" json:"scaling_model,omitempty"`
}

// ServiceResourceAccess describes how to reach the service once running.
// Populated for HOSTED instances (endpoint already known) and as a template for DEPLOYMENT.
type ServiceResourceAccess struct {
	Container       *models.Container    `json:"container,omitempty" bson:"container,omitempty"`
	Protocol        live.ServiceProtocol `json:"protocol" bson:"protocol" default:"0"`
	EndpointPattern string               `json:"endpoint_pattern,omitempty" bson:"endpoint_pattern,omitempty"`
	HealthCheckPath string               `json:"health_check_path,omitempty" bson:"health_check_path,omitempty"`
}

type ServiceResource struct {
	AbstractInstanciatedResource[*ServiceInstance]
	Infrastructure enum.InfrastructureType `json:"infrastructure" bson:"infrastructure" default:"-1"`
	Usage          *ServiceUsage           `bson:"usage,omitempty" json:"usage,omitempty"`
	OpenSource     bool                    `json:"open_source" bson:"open_source" default:"false"`
	License        string                  `json:"license,omitempty" bson:"license,omitempty"`
	Maturity       string                  `json:"maturity,omitempty" bson:"maturity,omitempty"`
}

func (r *ServiceResource) GetType() string {
	return tools.SERVICE_RESOURCE.String()
}

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

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

type ServiceInstance struct {
	ResourceInstance[*ServiceResourcePartnership]
	Mode          ServiceMode            `json:"mode" bson:"mode" default:"0"`
	Access        *ServiceResourceAccess `json:"access,omitempty" bson:"access,omitempty"`
	MaxConcurrent int                    `json:"max_concurrent,omitempty" bson:"max_concurrent,omitempty"`
}

func (ri *ServiceInstance) IsPeerless() bool { return false }

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

type ServiceResourcePartnership struct {
	ResourcePartnerShip[*ServiceResourcePricingProfile]
}

// ServiceResourcePricingProfile handles both service modes:
//   - DEPLOYMENT: uptime billing via ExploitPricingProfile (pay while service is up)
//   - HOSTED:     per-call billing via AccessPricingProfile (pay per request)
type ServiceResourcePricingProfile struct {
	Mode          ServiceMode                                                 `json:"mode" bson:"mode"`
	UptimePricing *pricing.ExploitPricingProfile[pricing.TimePricingStrategy] `json:"uptime_pricing,omitempty" bson:"uptime_pricing,omitempty"`
	AccessPricing *pricing.AccessPricingProfile[pricing.TimePricingStrategy]  `json:"access_pricing,omitempty" bson:"access_pricing,omitempty"`
}

func (p *ServiceResourcePricingProfile) ensure() {
	if p.UptimePricing == nil {
		p.UptimePricing = &pricing.ExploitPricingProfile[pricing.TimePricingStrategy]{}
	}
	if p.AccessPricing == nil {
		p.AccessPricing = &pricing.AccessPricingProfile[pricing.TimePricingStrategy]{}
	}
}

func (p *ServiceResourcePricingProfile) IsPurchasable() bool {
	p.ensure()
	if p.Mode == DEPLOYMENT {
		return p.UptimePricing.IsPurchasable()
	}
	return p.AccessPricing.IsPurchasable()
}

func (p *ServiceResourcePricingProfile) IsBooked() bool {
	p.ensure()
	if p.Mode == DEPLOYMENT {
		return p.UptimePricing.IsBooked()
	}
	return p.AccessPricing.IsBooked()
}

func (p *ServiceResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
	p.ensure()
	if p.Mode == DEPLOYMENT {
		return p.UptimePricing.GetPurchase()
	}
	return p.AccessPricing.GetPurchase()
}

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

func (p *ServiceResourcePricingProfile) GetPriceHT(quantity float64, val float64, start time.Time, end time.Time, variations []*pricing.PricingVariation, params ...string) (float64, error) {
	p.ensure()
	if p.Mode == DEPLOYMENT {
		return p.UptimePricing.GetPriceHT(quantity, val, start, end, variations, params...)
	}
	return p.AccessPricing.GetPriceHT(quantity, val, start, end, variations, params...)
}

type PricedServiceResource struct {
	PricedResource[*ServiceResourcePricingProfile]
}

func (r *PricedServiceResource) ensurePricing() {
	if r.SelectedPricing == nil {
		r.SelectedPricing = &ServiceResourcePricingProfile{}
	}
}

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

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

func (r *PricedServiceResource) GetType() tools.DataType {
	return tools.SERVICE_RESOURCE
}

func (r *PricedServiceResource) GetPriceHT() (float64, error) {
	r.ensurePricing()
	return r.PricedResource.GetPriceHT()
}

// GetExplicitDurationInS returns -1 for DEPLOYMENT (unbounded uptime).
// For HOSTED, returns the actual call window duration.
func (a *PricedServiceResource) GetExplicitDurationInS() float64 {
	a.ensurePricing()
	if a.SelectedPricing.Mode == DEPLOYMENT {
		return -1
	}
	if a.BookingConfiguration == nil {
		a.BookingConfiguration = &BookingConfiguration{}
	}
	if a.BookingConfiguration.ExplicitBookingDurationS != 0 {
		return a.BookingConfiguration.ExplicitBookingDurationS
	}
	if a.BookingConfiguration.UsageStart == nil || a.BookingConfiguration.UsageEnd == nil {
		return -1 // no deadline specified: open-ended
	}
	return a.BookingConfiguration.UsageEnd.Sub(*a.BookingConfiguration.UsageStart).Seconds()
}
