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/utils"
	"cloud.o-forge.io/core/oc-lib/tools"
	"github.com/google/uuid"
)

// EmbeddedStorageSelection records which storage capability was activated on a
// compute unit graph node, and which pricing options were selected for it.
// Key in WorkflowExecution.SelectedEmbeddedStorages is the compute graph node ID.
// A nil/absent entry means no storage was activated on that compute unit.
type EmbeddedStorageSelection struct {
	StorageIndex     int `json:"storage_index" bson:"storage_index"`         // index in ComputeResourceInstance.AvailableStorages
	PartnershipIndex int `json:"partnership_index" bson:"partnership_index"` // index in the storage's partnerships
	BuyingIndex      int `json:"buying_index" bson:"buying_index"`
	StrategyIndex    int `json:"strategy_index" bson:"strategy_index"`
}

/*
* StorageResource is a struct that represents a storage resource
* it defines the resource storage
 */
type StorageResource struct {
	AbstractInstanciatedResource[*StorageResourceInstance]                  // AbstractResource contains the basic fields of an object (id, name)
	StorageType                                            enum.StorageType `bson:"storage_type" json:"storage_type" default:"-1"` // Type is the type of the storage
	Acronym                                                string           `bson:"acronym,omitempty" json:"acronym,omitempty"`    // Acronym is the acronym of the storage
}

func (d *StorageResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
	return NewAccessor[*StorageResource](tools.STORAGE_RESOURCE, request) // Create a new instance of the accessor
}

func (r *StorageResource) GetType() string {
	return tools.STORAGE_RESOURCE.String()
}

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

func (ri *StorageResource) StoreDraftDefault() {
	ri.AbstractObject.StoreDraftDefault()
	ri.Env = append(ri.Env, models.Param{
		Attr:     "source",
		Value:    "[resource]instance.source",
		Readonly: true,
	})
}

type StorageResourceInstance struct {
	ResourceInstance[*StorageResourcePartnership]
	Source        string           `bson:"source,omitempty" json:"source,omitempty"` // Source is the source of the storage
	Path          string           `bson:"path,omitempty" json:"path,omitempty"`     // Path is the store folders in the source
	Local         bool             `bson:"local" json:"local"`
	SecurityLevel string           `bson:"security_level,omitempty" json:"security_level,omitempty"`
	SizeType      enum.StorageSize `bson:"size_type" json:"size_type" default:"0"`           // SizeType is the type of the storage size
	SizeGB        int64            `bson:"size,omitempty" json:"size,omitempty"`             // Size is the size of the storage
	Encryption    bool             `bson:"encryption,omitempty" json:"encryption,omitempty"` // Encryption is a flag that indicates if the storage is encrypted
	Redundancy    string           `bson:"redundancy,omitempty" json:"redundancy,omitempty"` // Redundancy is the redundancy of the storage
	Throughput    string           `bson:"throughput,omitempty" json:"throughput,omitempty"` // Throughput is the throughput of the storage
}

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

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

type StorageResourcePartnership struct {
	ResourcePartnerShip[*StorageResourcePricingProfile]
	MaxSizeGBAllowed     float64 `json:"allowed_gb,omitempty" bson:"allowed_gb,omitempty"`
	OnlyEncryptedAllowed bool    `json:"personal_data_allowed,omitempty" bson:"personal_data_allowed,omitempty"`
}

type PrivilegeStoragePricingStrategy int

const (
	BASIC_STORAGE PrivilegeStoragePricingStrategy = iota
	GARANTED_ON_DELAY_STORAGE
	GARANTED_STORAGE
)

func PrivilegeStoragePricingStrategyList() []PrivilegeStoragePricingStrategy {
	return []PrivilegeStoragePricingStrategy{BASIC_STORAGE, GARANTED_ON_DELAY_STORAGE, GARANTED_STORAGE}
}

func (t PrivilegeStoragePricingStrategy) String() string {
	return [...]string{"NO MEMORY HOLDING", "KEEPED ON MEMORY GARANTED DURING DELAY", "KEEPED ON MEMORY GARANTED"}[t]
}

type StorageResourcePricingStrategy int

const (
	PER_DATA_STORED StorageResourcePricingStrategy = iota + 7
	PER_TB_STORED
	PER_GB_STORED
	PER_MB_STORED
	PER_KB_STORED
)

func StorageResourcePricingStrategyList() []StorageResourcePricingStrategy {
	return []StorageResourcePricingStrategy{PER_DATA_STORED, PER_TB_STORED, PER_GB_STORED, PER_MB_STORED, PER_KB_STORED}
}

func (t StorageResourcePricingStrategy) String() string {
	l := pricing.TimePricingStrategyListStr()
	l = append(l, []string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}...)
	return l[t]
}

func (t StorageResourcePricingStrategy) GetStrategy() string {
	l := pricing.TimePricingStrategyListStr()
	l = append(l, []string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}...)
	return l[t]
}

func (t StorageResourcePricingStrategy) GetStrategyValue() int {
	return int(t)
}

func ToStorageResourcePricingStrategy(i int) StorageResourcePricingStrategy {
	return StorageResourcePricingStrategy(i)
}

func (t StorageResourcePricingStrategy) GetQuantity(amountOfDataGB float64) (float64, error) {
	switch t {
	case PER_DATA_STORED:
		return amountOfDataGB, nil
	case PER_TB_STORED:
		return amountOfDataGB * 1000, nil
	case PER_GB_STORED:
		return amountOfDataGB, nil
	case PER_MB_STORED:
		return (amountOfDataGB * 1000), nil
	case PER_KB_STORED:
		return amountOfDataGB * 1000000, nil
	}
	return 0, errors.New("pricing strategy not found")
}

type StorageResourcePricingProfile struct {
	pricing.ExploitPricingProfile[StorageResourcePricingStrategy] // ExploitPricingProfile is the pricing profile of a storage it means that we exploit the resource for an amount of continuous time
}

func (p *StorageResourcePricingProfile) IsPurchasable() bool {
	return p.Pricing.BuyingStrategy == pricing.PERMANENT
}

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

type PricedStorageResource struct {
	PricedResource[*StorageResourcePricingProfile]
	UsageStorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"`
}

func (r *PricedStorageResource) ensurePricing() {
	if r.SelectedPricing == nil {
		r.SelectedPricing = &StorageResourcePricingProfile{}
	}
}

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

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

func (r *PricedStorageResource) GetType() tools.DataType {
	return tools.STORAGE_RESOURCE
}

func (r *PricedStorageResource) 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
	var err error
	amountOfData := float64(1)
	if pricing.GetOverrideStrategyValue() >= 0 {
		amountOfData, err = ToStorageResourcePricingStrategy(pricing.GetOverrideStrategyValue()).GetQuantity(r.UsageStorageGB)
		if err != nil {
			return 0, err
		}
	}
	return pricing.GetPriceHT(amountOfData, r.BookingConfiguration.ExplicitBookingDurationS,
		*r.BookingConfiguration.UsageStart, *r.BookingConfiguration.UsageEnd, r.Variations)
}
