package resources

import (
	"errors"
	"time"

	"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"
)

/*
* DataResource is a struct that represents a data resource
* it defines the resource data
 */
type DataResource struct {
	AbstractInstanciatedResource[*DataInstance]
	Type                   string     `bson:"type,omitempty" json:"type,omitempty"`
	Quality                string     `bson:"quality,omitempty" json:"quality,omitempty"`
	OpenData               bool       `bson:"open_data" json:"open_data" default:"false"` // Type is the type of the storage
	Static                 bool       `bson:"static" json:"static" default:"false"`
	UpdatePeriod           *time.Time `bson:"update_period,omitempty" json:"update_period,omitempty"`
	PersonalData           bool       `bson:"personal_data,omitempty" json:"personal_data,omitempty"`
	AnonymizedPersonalData bool       `bson:"anonymized_personal_data,omitempty" json:"anonymized_personal_data,omitempty"`
	SizeGB                 float64    `json:"size,omitempty" bson:"size,omitempty"` // SizeGB is the size of the data	License              DataLicense `json:"license" bson:"license" description:"license of the data" default:"0"` // License is the license of the data
	// ? Interest               DataLicense `json:"interest" bson:"interest" description:"interest of the data" default:"0"`      // Interest is the interest of the data
	Example string `json:"example,omitempty" bson:"example,omitempty" description:"base64 encoded data"` // Example is an example of the data
}

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

func (r *DataResource) GetType() string {
	return tools.DATA_RESOURCE.String()
}

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

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

type DataInstance struct {
	ResourceInstance[*DataResourcePartnership]
	Access *ResourceAccess `json:"access,omitempty" bson:"access,omitempty"`
}

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

type DataResourcePartnership struct {
	ResourcePartnerShip[*DataResourcePricingProfile]
	MaxDownloadableGbAllowed      float64 `json:"allowed_gb,omitempty" bson:"allowed_gb,omitempty"`
	PersonalDataAllowed           bool    `json:"personal_data_allowed,omitempty" bson:"personal_data_allowed,omitempty"`
	AnonymizedPersonalDataAllowed bool    `json:"anonymized_personal_data_allowed,omitempty" bson:"anonymized_personal_data_allowed,omitempty"`
}

type DataResourcePricingStrategy int

const (
	PER_DOWNLOAD DataResourcePricingStrategy = iota + 7
	PER_TB_DOWNLOADED
	PER_GB_DOWNLOADED
	PER_MB_DOWNLOADED
	PER_KB_DOWNLOADED
)

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

func DataResourcePricingStrategyList() []DataResourcePricingStrategy {
	return []DataResourcePricingStrategy{PER_DOWNLOAD, PER_TB_DOWNLOADED, PER_GB_DOWNLOADED, PER_MB_DOWNLOADED, PER_KB_DOWNLOADED}
}

func ToDataResourcePricingStrategy(i int) DataResourcePricingStrategy {
	return DataResourcePricingStrategy(i)
}

func (t DataResourcePricingStrategy) 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 DataResourcePricingStrategy) GetStrategyValue() int {
	return int(t)
}

func (t DataResourcePricingStrategy) GetQuantity(amountOfDataGB float64) (float64, error) {
	switch t {
	case PER_DOWNLOAD:
		return 1, nil
	case PER_TB_DOWNLOADED:
		return amountOfDataGB * 1000, nil
	case PER_GB_DOWNLOADED:
		return amountOfDataGB, nil
	case PER_MB_DOWNLOADED:
		return amountOfDataGB / 1000, nil
	case PER_KB_DOWNLOADED:
		return amountOfDataGB / 1000000, nil
	}
	return 0, errors.New("pricing strategy not found")
}

type DataResourcePricingProfile struct {
	pricing.AccessPricingProfile[DataResourcePricingStrategy] // AccessPricingProfile is the pricing profile of a data it means that we can access the data for an amount of time
}

func (p *DataResourcePricingProfile) GetOverrideStrategyValue() int {
	return p.Pricing.OverrideStrategy.GetStrategyValue()
}

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

func (p *DataResourcePricingProfile) IsBooked() bool {
	// TODO WHAT ABOUT PAY PER USE... it's a complicate CASE
	return p.Pricing.BuyingStrategy != pricing.PERMANENT
}

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

func (r *PricedDataResource) ensurePricing() {
	if r.SelectedPricing == nil {
		r.SelectedPricing = &DataResourcePricingProfile{}
	}
}

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

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

func (r *PricedDataResource) GetType() tools.DataType {
	return tools.DATA_RESOURCE
}

func (r *PricedDataResource) 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 = ToDataResourcePricingStrategy(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)
}
