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

/*
* DataResource is a struct that represents a data resource
* it defines the resource data
 */
type DataResource struct {
	AbstractIntanciatedResource[*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, func() utils.DBObject { return &DataResource{} }) // Create a new instance of the accessor
}

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

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

type DataInstance struct {
	ResourceInstance[*DataResourcePartnership]
	Source string `json:"source" bson:"source"` // Source is the source of the data
}

func (ri *DataInstance) StoreDraftDefault() {
	found := false
	for _, p := range ri.ResourceInstance.Outputs {
		if p.Attr == "source" {
			found = true
			break
		}
	}
	if !found {
		ri.ResourceInstance.Outputs = append(ri.ResourceInstance.Outputs, models.Param{
			Attr:     "source",
			Value:    ri.Source,
			Readonly: true,
		})
	}
	ri.ResourceInstance.StoreDraftDefault()
}

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
	PER_TB_DOWNLOADED
	PER_GB_DOWNLOADED
	PER_MB_DOWNLOADED
	PER_KB_DOWNLOADED
)

func (t DataResourcePricingStrategy) String() string {
	return [...]string{"PER DOWNLOAD", "PER TB DOWNLOADED", "PER GB DOWNLOADED", "PER MB DOWNLOADED", "PER KB DOWNLOADED"}[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 {
	return [...]string{"PER_DOWNLOAD", "PER_GB", "PER_MB", "PER_KB"}[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) GetPrice(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, params ...string) (float64, error) {
	return p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end)
}

func (p *DataResourcePricingProfile) IsPurchased() bool {
	return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE
}

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

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

func (r *PricedDataResource) 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
	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.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd)
}