package resources

import (
	"slices"

	"cloud.o-forge.io/core/oc-lib/config"
	"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/peer"
	"cloud.o-forge.io/core/oc-lib/models/utils"
	"cloud.o-forge.io/core/oc-lib/tools"
	"github.com/biter777/countries"
)

// AbstractResource is the struct containing all of the attributes commons to all ressources
type AbstractResource struct {
	utils.AbstractObject                // AbstractObject contains the basic fields of an object (id, name)
	Type                  string        `json:"type,omitempty" bson:"type,omitempty"`                                               // Type is the type of the resource
	Logo                  string        `json:"logo,omitempty" bson:"logo,omitempty" validate:"required"`                           // Logo is the logo of the resource
	Description           string        `json:"description,omitempty" bson:"description,omitempty"`                                 // Description is the description of the resource
	ShortDescription      string        `json:"short_description,omitempty" bson:"short_description,omitempty" validate:"required"` // ShortDescription is the short description of the resource
	Owners                []utils.Owner `json:"owners,omitempty" bson:"owners,omitempty"`                                           // Owners is the list of owners of the resource
	UsageRestrictions     string        `bson:"usage_restrictions,omitempty" json:"usage_restrictions,omitempty"`
	SelectedInstanceIndex *int          `json:"selected_instance_index,omitempty" bson:"selected_instance_index,omitempty"` // SelectedInstance is the selected instance
}

func (r *AbstractResource) GetSelectedInstance() utils.DBObject {
	return nil
}

func (r *AbstractResource) GetType() string {
	return tools.INVALID.String()
}

func (r *AbstractResource) StoreDraftDefault() {
	r.IsDraft = true
}

func (r *AbstractResource) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
	if r.IsDraft != set.IsDrafted() && set.IsDrafted() {
		return true, set // only state can be updated
	}
	return r.IsDraft != set.IsDrafted() && set.IsDrafted(), set
}

func (r *AbstractResource) CanDelete() bool {
	return r.IsDraft // only draft bookings can be deleted
}

type AbstractInstanciatedResource[T ResourceInstanceITF] struct {
	AbstractResource     // AbstractResource contains the basic fields of an object (id, name)
	Instances        []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource            // Bill is the bill of the resource
}

func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource(
	t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
	instances := map[string]string{}
	profiles := []pricing.PricingProfileITF{}
	for _, instance := range abs.Instances {
		instances[instance.GetID()] = instance.GetName()
		profiles = instance.GetPricingsProfiles(request.PeerID, request.Groups)
	}
	return &PricedResource{
		Name:            abs.Name,
		Logo:            abs.Logo,
		ResourceID:      abs.UUID,
		ResourceType:    t,
		InstancesRefs:   instances,
		PricingProfiles: profiles,
		CreatorID:       abs.CreatorID,
	}
}

func (abs *AbstractInstanciatedResource[T]) ClearEnv() utils.DBObject {
	for _, instance := range abs.Instances {
		instance.ClearEnv()
	}
	return abs
}

func (r *AbstractInstanciatedResource[T]) GetSelectedInstance() utils.DBObject {
	if r.SelectedInstanceIndex != nil && len(r.Instances) > *r.SelectedInstanceIndex {
		return r.Instances[*r.SelectedInstanceIndex]
	}
	if len(r.Instances) > 0 {
		return r.Instances[0]
	}
	return nil
}

func (abs *AbstractInstanciatedResource[T]) SetAllowedInstances(request *tools.APIRequest) {
	if request != nil && request.PeerID == abs.CreatorID && request.PeerID != "" {
		return
	}
	abs.Instances = verifyAuthAction[T](abs.Instances, request)
}

func (d *AbstractInstanciatedResource[T]) Trim() {
	d.Type = d.GetType()
	if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: d.CreatorID}}).IsMySelf(); !ok {
		for _, instance := range d.Instances {
			instance.ClearPeerGroups()
		}
	}
}

func (abs *AbstractInstanciatedResource[T]) VerifyAuth(request *tools.APIRequest) bool {
	return len(verifyAuthAction[T](abs.Instances, request)) > 0 || abs.AbstractObject.VerifyAuth(request)
}

func verifyAuthAction[T ResourceInstanceITF](baseInstance []T, request *tools.APIRequest) []T {
	instances := []T{}
	for _, instance := range baseInstance {
		_, peerGroups := instance.GetPeerGroups()
		for _, peers := range peerGroups {
			if request == nil {
				continue
			}
			if grps, ok := peers[request.PeerID]; ok || config.GetConfig().Whitelist {
				if (ok && slices.Contains(grps, "*")) || (!ok && config.GetConfig().Whitelist) {
					instances = append(instances, instance)
					continue
				}
				for _, grp := range grps {
					if slices.Contains(request.Groups, grp) {
						instances = append(instances, instance)
					}
				}
			}
		}
	}
	return instances
}

type GeoPoint struct {
	Latitude  float64 `json:"latitude,omitempty" bson:"latitude,omitempty"`
	Longitude float64 `json:"longitude,omitempty" bson:"longitude,omitempty"`
}

type Credentials struct {
	Login string `json:"login,omitempty" bson:"login,omitempty"`
	Pass  string `json:"password,omitempty" bson:"password,omitempty"`
}

type ResourceInstance[T ResourcePartnerITF] struct {
	utils.AbstractObject
	Location       GeoPoint              `json:"location,omitempty" bson:"location,omitempty"`
	Country        countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"`
	AccessProtocol string                `json:"access_protocol,omitempty" bson:"access_protocol,omitempty"`
	Env            []models.Param        `json:"env,omitempty" bson:"env,omitempty"`
	Inputs         []models.Param        `json:"inputs,omitempty" bson:"inputs,omitempty"`
	Outputs        []models.Param        `json:"outputs,omitempty" bson:"outputs,omitempty"`
	Partnerships   []T                   `json:"partnerships,omitempty" bson:"partnerships,omitempty"`
}

func (ri *ResourceInstance[T]) ClearEnv() {
	ri.Env = []models.Param{}
	ri.Inputs = []models.Param{}
	ri.Outputs = []models.Param{}
}

func (ri *ResourceInstance[T]) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
	pricings := []pricing.PricingProfileITF{}
	for _, p := range ri.Partnerships {
		pricings = append(pricings, p.GetPricingsProfiles(peerID, groups)...)
	}
	return pricings
}

func (ri *ResourceInstance[T]) GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string) {
	groups := []map[string][]string{}
	partners := []ResourcePartnerITF{}
	for _, p := range ri.Partnerships {
		partners = append(partners, p)
		groups = append(groups, p.GetPeerGroups())
	}
	return partners, groups
}

func (ri *ResourceInstance[T]) ClearPeerGroups() {
	for _, p := range ri.Partnerships {
		p.ClearPeerGroups()
	}
}

type ResourcePartnerShip[T pricing.PricingProfileITF] struct {
	Namespace       string              `json:"namespace" bson:"namespace" default:"default-namespace"`
	PeerGroups      map[string][]string `json:"peer_groups,omitempty" bson:"peer_groups,omitempty"`
	PricingProfiles []T                 `json:"pricing_profiles,omitempty" bson:"pricing_profiles,omitempty"`
}

func (ri *ResourcePartnerShip[T]) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
	profiles := []pricing.PricingProfileITF{}
	if ri.PeerGroups[peerID] != nil {
		for _, ri := range ri.PricingProfiles {
			profiles = append(profiles, ri)
		}
		if slices.Contains(groups, "*") {
			for _, ri := range ri.PricingProfiles {
				profiles = append(profiles, ri)
			}
			return profiles
		}
		for _, p := range ri.PeerGroups[peerID] {
			if slices.Contains(groups, p) {
				for _, ri := range ri.PricingProfiles {
					profiles = append(profiles, ri)
				}
				return profiles
			}
		}
	}
	return profiles
}

func (rp *ResourcePartnerShip[T]) GetPeerGroups() map[string][]string {
	return rp.PeerGroups
}

func (rp *ResourcePartnerShip[T]) ClearPeerGroups() {
	rp.PeerGroups = map[string][]string{}
}