package opencost

import (
	"bytes"
	"fmt"
	"reflect"
	"time"

	"github.com/opencost/opencost/core/pkg/log"
	"github.com/opencost/opencost/core/pkg/util/json"
)

// Encoding and decoding logic for Asset types

// Any marshal and unmarshal

// MarshalJSON implements json.Marshaler
func (a *Any) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncode(buffer, "properties", a.Properties, ",")
	jsonEncode(buffer, "labels", a.Labels, ",")
	jsonEncode(buffer, "window", a.Window, ",")
	jsonEncodeString(buffer, "start", a.Start.Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", a.End.Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", a.Minutes(), ",")
	jsonEncodeFloat64(buffer, "adjustment", a.Adjustment, ",")
	jsonEncodeFloat64(buffer, "totalCost", a.TotalCost(), "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (a *Any) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = a.InterfaceToAny(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to Any, carrying over relevant fields
func (a *Any) InterfaceToAny(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	// parse start and end strings to time.Time
	start, err := time.Parse(time.RFC3339, fmap["start"].(string))
	if err != nil {
		return err
	}
	end, err := time.Parse(time.RFC3339, fmap["end"].(string))
	if err != nil {
		return err
	}

	a.Properties = &properties
	a.Labels = labels
	a.Start = start
	a.End = end

	if _, found := fmap["window"]; found {
		a.Window = toWindow(fmap["window"].(map[string]interface{}))
	}

	if adjustment, err := getTypedVal(fmap["adjustment"]); err == nil {
		a.Adjustment = adjustment.(float64)
	}
	if Cost, err := getTypedVal(fmap["totalCost"]); err == nil {
		a.Cost = Cost.(float64) - a.Adjustment
	}

	return nil
}

// Cloud marshal and unmarshal

// MarshalJSON implements json.Marshaler
func (ca *Cloud) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncodeString(buffer, "type", ca.Type().String(), ",")
	jsonEncode(buffer, "properties", ca.Properties, ",")
	jsonEncode(buffer, "labels", ca.Labels, ",")
	jsonEncode(buffer, "window", ca.Window, ",")
	jsonEncodeString(buffer, "start", ca.Start.Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", ca.End.Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", ca.Minutes(), ",")
	jsonEncodeFloat64(buffer, "adjustment", ca.Adjustment, ",")
	jsonEncodeFloat64(buffer, "credit", ca.Credit, ",")
	jsonEncodeFloat64(buffer, "totalCost", ca.TotalCost(), "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (ca *Cloud) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = ca.InterfaceToCloud(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to Cloud, carrying over relevant fields
func (ca *Cloud) InterfaceToCloud(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	// parse start and end strings to time.Time
	start, err := time.Parse(time.RFC3339, fmap["start"].(string))
	if err != nil {
		return err
	}
	end, err := time.Parse(time.RFC3339, fmap["end"].(string))
	if err != nil {
		return err
	}

	ca.Properties = &properties
	ca.Labels = labels
	ca.Start = start
	ca.End = end
	if _, found := fmap["window"]; found {
		ca.Window = toWindow(fmap["window"].(map[string]interface{}))
	}

	if adjustment, err := getTypedVal(fmap["adjustment"]); err == nil {
		ca.Adjustment = adjustment.(float64)
	}
	if Credit, err := getTypedVal(fmap["credit"]); err == nil {
		ca.Credit = Credit.(float64)
	}
	if Cost, err := getTypedVal(fmap["totalCost"]); err == nil {
		ca.Cost = Cost.(float64) - ca.Adjustment - ca.Credit
	}

	return nil
}

// ClusterManagement marshal and unmarshal

// MarshalJSON implements json.Marshler
func (cm *ClusterManagement) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncodeString(buffer, "type", cm.Type().String(), ",")
	jsonEncode(buffer, "properties", cm.Properties, ",")
	jsonEncode(buffer, "labels", cm.Labels, ",")
	jsonEncode(buffer, "window", cm.Window, ",")
	jsonEncodeString(buffer, "start", cm.GetStart().Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", cm.GetEnd().Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", cm.Minutes(), ",")
	jsonEncodeFloat64(buffer, "totalCost", cm.TotalCost(), "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (cm *ClusterManagement) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = cm.InterfaceToClusterManagement(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to ClusterManagement, carrying over relevant fields
func (cm *ClusterManagement) InterfaceToClusterManagement(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	cm.Properties = &properties
	cm.Labels = labels
	if _, found := fmap["window"]; found {
		cm.Window = toWindow(fmap["window"].(map[string]interface{}))
	}

	if Cost, err := getTypedVal(fmap["totalCost"]); err == nil {
		cm.Cost = Cost.(float64)
	}

	return nil
}

// Disk marshal and unmarshal

// MarshalJSON implements the json.Marshaler interface
func (d *Disk) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncodeString(buffer, "type", d.Type().String(), ",")
	jsonEncode(buffer, "properties", d.Properties, ",")
	jsonEncode(buffer, "labels", d.Labels, ",")
	jsonEncode(buffer, "window", d.Window, ",")
	jsonEncodeString(buffer, "start", d.Start.Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", d.End.Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", d.Minutes(), ",")
	jsonEncodeFloat64(buffer, "byteHours", d.ByteHours, ",")
	jsonEncodeFloat64(buffer, "bytes", d.Bytes(), ",")
	if d.ByteHoursUsed == nil {
		jsonEncode(buffer, "byteHoursUsed", nil, ",")
	} else {
		jsonEncodeFloat64(buffer, "byteHoursUsed", *d.ByteHoursUsed, ",")
	}
	if d.ByteUsageMax == nil {
		jsonEncode(buffer, "byteUsageMax", nil, ",")
	} else {
		jsonEncodeFloat64(buffer, "byteUsageMax", *d.ByteUsageMax, ",")
	}
	jsonEncode(buffer, "breakdown", d.Breakdown, ",")
	jsonEncodeFloat64(buffer, "adjustment", d.Adjustment, ",")
	jsonEncodeFloat64(buffer, "totalCost", d.TotalCost(), ",")
	jsonEncodeString(buffer, "storageClass", d.StorageClass, ",")
	jsonEncodeString(buffer, "volumeName", d.VolumeName, ",")
	jsonEncodeString(buffer, "claimName", d.ClaimName, ",")
	jsonEncodeFloat64(buffer, "local", d.Local, ",")
	jsonEncodeString(buffer, "claimNamespace", d.ClaimNamespace, "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (d *Disk) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = d.InterfaceToDisk(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to Disk, carrying over relevant fields
func (d *Disk) InterfaceToDisk(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	// parse start and end strings to time.Time
	start, err := time.Parse(time.RFC3339, fmap["start"].(string))
	if err != nil {
		return err
	}
	end, err := time.Parse(time.RFC3339, fmap["end"].(string))
	if err != nil {
		return err
	}

	fbreakdown := fmap["breakdown"].(map[string]interface{})

	breakdown := toBreakdown(fbreakdown)

	d.Properties = &properties
	d.Labels = labels
	d.Start = start
	d.End = end
	if _, found := fmap["window"]; found {
		d.Window = toWindow(fmap["window"].(map[string]interface{}))
	}
	d.Breakdown = &breakdown

	if adjustment, err := getTypedVal(fmap["adjustment"]); err == nil {
		d.Adjustment = adjustment.(float64)
	}
	if Cost, err := getTypedVal(fmap["totalCost"]); err == nil {
		d.Cost = Cost.(float64) - d.Adjustment
	}
	if ByteHours, err := getTypedVal(fmap["byteHours"]); err == nil {
		d.ByteHours = ByteHours.(float64)
	}
	if ByteHoursUsed, err := getTypedVal(fmap["byteHoursUsed"]); err == nil {
		if ByteHoursUsed == nil {
			d.ByteHoursUsed = nil
		} else {
			byteHours := ByteHoursUsed.(float64)
			d.ByteHoursUsed = &byteHours
		}
	}
	if ByteUsageMax, err := getTypedVal(fmap["byteUsageMax"]); err == nil {
		if ByteUsageMax == nil {
			d.ByteUsageMax = nil
		} else {
			max := ByteUsageMax.(float64)
			d.ByteUsageMax = &max
		}
	}

	if StorageClass, err := getTypedVal(fmap["storageClass"]); err == nil {
		d.StorageClass = StorageClass.(string)
	}
	if VolumeName, err := getTypedVal(fmap["volumeName"]); err == nil {
		d.VolumeName = VolumeName.(string)
	}
	if ClaimName, err := getTypedVal(fmap["claimName"]); err == nil {
		d.ClaimName = ClaimName.(string)
	}
	if ClaimNamespace, err := getTypedVal(fmap["claimNamespace"]); err == nil {
		d.ClaimNamespace = ClaimNamespace.(string)
	}

	if local, err := getTypedVal(fmap["local"]); err == nil {
		d.Local = local.(float64)
	}

	return nil

}

// Network marshal and unmarshal

// MarshalJSON implements json.Marshal interface
func (n *Network) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncodeString(buffer, "type", n.Type().String(), ",")
	jsonEncode(buffer, "properties", n.Properties, ",")
	jsonEncode(buffer, "labels", n.Labels, ",")
	jsonEncode(buffer, "window", n.Window, ",")
	jsonEncodeString(buffer, "start", n.Start.Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", n.End.Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", n.Minutes(), ",")
	jsonEncodeFloat64(buffer, "adjustment", n.Adjustment, ",")
	jsonEncodeFloat64(buffer, "totalCost", n.TotalCost(), "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (n *Network) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = n.InterfaceToNetwork(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to Network, carrying over relevant fields
func (n *Network) InterfaceToNetwork(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	// parse start and end strings to time.Time
	start, err := time.Parse(time.RFC3339, fmap["start"].(string))
	if err != nil {
		return err
	}
	end, err := time.Parse(time.RFC3339, fmap["end"].(string))
	if err != nil {
		return err
	}

	n.Properties = &properties
	n.Labels = labels
	n.Start = start
	n.End = end
	if _, found := fmap["window"]; found {
		n.Window = toWindow(fmap["window"].(map[string]interface{}))
	}

	if adjustment, err := getTypedVal(fmap["adjustment"]); err == nil {
		n.Adjustment = adjustment.(float64)
	}
	if Cost, err := getTypedVal(fmap["totalCost"]); err == nil {
		n.Cost = Cost.(float64) - n.Adjustment
	}

	return nil

}

// Node marshal and unmarshal

// MarshalJSON implements json.Marshal interface
func (n *Node) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncodeString(buffer, "type", n.Type().String(), ",")
	jsonEncode(buffer, "properties", n.Properties, ",")
	jsonEncode(buffer, "labels", n.Labels, ",")
	jsonEncode(buffer, "window", n.Window, ",")
	jsonEncodeString(buffer, "start", n.Start.Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", n.End.Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", n.Minutes(), ",")
	jsonEncodeString(buffer, "nodeType", n.NodeType, ",")
	if poolName := GetNodePoolName(n.Properties.Provider, n.Labels); poolName != "" {
		jsonEncodeString(buffer, "pool", poolName, ",")
	}
	jsonEncodeFloat64(buffer, "cpuCores", n.CPUCores(), ",")
	jsonEncodeFloat64(buffer, "ramBytes", n.RAMBytes(), ",")
	jsonEncodeFloat64(buffer, "cpuCoreHours", n.CPUCoreHours, ",")
	jsonEncodeFloat64(buffer, "ramByteHours", n.RAMByteHours, ",")
	jsonEncodeFloat64(buffer, "GPUHours", n.GPUHours, ",")
	jsonEncode(buffer, "cpuBreakdown", n.CPUBreakdown, ",")
	jsonEncode(buffer, "ramBreakdown", n.RAMBreakdown, ",")
	jsonEncodeFloat64(buffer, "preemptible", n.Preemptible, ",")
	jsonEncodeFloat64(buffer, "discount", n.Discount, ",")
	jsonEncodeFloat64(buffer, "cpuCost", n.CPUCost, ",")
	jsonEncodeFloat64(buffer, "gpuCost", n.GPUCost, ",")
	jsonEncodeFloat64(buffer, "gpuCount", n.GPUs(), ",")
	jsonEncodeFloat64(buffer, "ramCost", n.RAMCost, ",")
	jsonEncodeFloat64(buffer, "adjustment", n.Adjustment, ",")
	if n.Overhead != nil {
		jsonEncode(buffer, "overhead", n.Overhead, ",")
	}
	jsonEncodeFloat64(buffer, "totalCost", n.TotalCost(), "")

	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (n *Node) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = n.InterfaceToNode(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to Node, carrying over relevant fields
func (n *Node) InterfaceToNode(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	if labelsInterface, ok := fmap["labels"].(map[string]interface{}); ok {
		for k, v := range labelsInterface {
			if strValue, ok := v.(string); ok {
				labels[k] = strValue
			}
		}
	}

	// parse start and end strings to time.Time
	start, err := time.Parse(time.RFC3339, fmap["start"].(string))
	if err != nil {
		return err
	}
	end, err := time.Parse(time.RFC3339, fmap["end"].(string))
	if err != nil {
		return err
	}

	fcpuBreakdown := fmap["cpuBreakdown"].(map[string]interface{})
	framBreakdown := fmap["ramBreakdown"].(map[string]interface{})

	cpuBreakdown := toBreakdown(fcpuBreakdown)
	ramBreakdown := toBreakdown(framBreakdown)

	n.Properties = &properties
	n.Labels = labels
	n.Start = start
	n.End = end
	if _, found := fmap["window"]; found {
		n.Window = toWindow(fmap["window"].(map[string]interface{}))
	}
	n.CPUBreakdown = &cpuBreakdown
	n.RAMBreakdown = &ramBreakdown

	if adjustment, err := getTypedVal(fmap["adjustment"]); err == nil {
		n.Adjustment = adjustment.(float64)
	}
	if NodeType, err := getTypedVal(fmap["nodeType"]); err == nil {
		n.NodeType = NodeType.(string)
	}

	if CPUCoreHours, err := getTypedVal(fmap["cpuCoreHours"]); err == nil {
		n.CPUCoreHours = CPUCoreHours.(float64)
	}
	if RAMByteHours, err := getTypedVal(fmap["ramByteHours"]); err == nil {
		n.RAMByteHours = RAMByteHours.(float64)
	}
	if GPUHours, err := getTypedVal(fmap["GPUHours"]); err == nil {
		n.GPUHours = GPUHours.(float64)
	}
	if CPUCost, err := getTypedVal(fmap["cpuCost"]); err == nil {
		n.CPUCost = CPUCost.(float64)
	}
	if GPUCost, err := getTypedVal(fmap["gpuCost"]); err == nil {
		n.GPUCost = GPUCost.(float64)
	}
	if GPUCount, err := getTypedVal(fmap["gpuCount"]); err == nil {
		n.GPUCount = GPUCount.(float64)
	}
	if RAMCost, err := getTypedVal(fmap["ramCost"]); err == nil {
		n.RAMCost = RAMCost.(float64)
	}
	if Discount, err := getTypedVal(fmap["discount"]); err == nil {
		n.Discount = Discount.(float64)
	}
	if Preemptible, err := getTypedVal(fmap["preemptible"]); err == nil {
		n.Preemptible = Preemptible.(float64)
	}

	return nil
}

// Loadbalancer marshal and unmarshal

// MarshalJSON implements json.Marshal
func (lb *LoadBalancer) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncodeString(buffer, "type", lb.Type().String(), ",")
	jsonEncode(buffer, "properties", lb.Properties, ",")
	jsonEncode(buffer, "labels", lb.Labels, ",")
	jsonEncode(buffer, "window", lb.Window, ",")
	jsonEncodeString(buffer, "start", lb.Start.Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", lb.End.Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", lb.Minutes(), ",")
	jsonEncodeFloat64(buffer, "adjustment", lb.Adjustment, ",")
	jsonEncodeFloat64(buffer, "totalCost", lb.TotalCost(), ",")
	jsonEncode(buffer, "private", lb.Private, ",")
	jsonEncodeString(buffer, "ip", lb.Ip, "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (lb *LoadBalancer) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = lb.InterfaceToLoadBalancer(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to LoadBalancer, carrying over relevant fields
func (lb *LoadBalancer) InterfaceToLoadBalancer(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	// parse start and end strings to time.Time
	start, err := time.Parse(time.RFC3339, fmap["start"].(string))
	if err != nil {
		return err
	}
	end, err := time.Parse(time.RFC3339, fmap["end"].(string))
	if err != nil {
		return err
	}

	lb.Properties = &properties
	lb.Labels = labels
	lb.Start = start
	lb.End = end
	if _, found := fmap["window"]; found {
		lb.Window = toWindow(fmap["window"].(map[string]interface{}))
	}

	if adjustment, err := getTypedVal(fmap["adjustment"]); err == nil {
		lb.Adjustment = adjustment.(float64)
	}
	if Cost, err := getTypedVal(fmap["totalCost"]); err == nil {
		lb.Cost = Cost.(float64) - lb.Adjustment
	}
	if private, err := getTypedVal(fmap["private"]); err == nil {
		lb.Private = private.(bool)
	}
	if ip, err := getTypedVal(fmap["ip"]); err == nil {
		lb.Ip = ip.(string)
	}

	return nil

}

// SharedAsset marshal and unmarshal

// MarshalJSON implements json.Marshaler
func (sa *SharedAsset) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncodeString(buffer, "type", sa.Type().String(), ",")
	jsonEncode(buffer, "properties", sa.Properties, ",")
	jsonEncode(buffer, "labels", sa.Labels, ",")
	jsonEncode(buffer, "window", sa.Window, ",")
	jsonEncodeString(buffer, "start", sa.GetStart().Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", sa.GetEnd().Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", sa.Minutes(), ",")
	jsonEncodeFloat64(buffer, "totalCost", sa.TotalCost(), "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (sa *SharedAsset) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = sa.InterfaceToSharedAsset(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to SharedAsset, carrying over relevant fields
func (sa *SharedAsset) InterfaceToSharedAsset(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	sa.Properties = &properties
	sa.Labels = labels
	if _, found := fmap["window"]; found {
		sa.Window = toWindow(fmap["window"].(map[string]interface{}))
	}

	if Cost, err := getTypedVal(fmap["totalCost"]); err == nil {
		sa.Cost = Cost.(float64)
	}

	return nil

}

// AssetSet marshal

// MarshalJSON JSON-encodes the AssetSet
func (as *AssetSet) MarshalJSON() ([]byte, error) {
	if as == nil {
		return json.Marshal(map[string]Asset{})
	}

	return json.Marshal(as.Assets)
}

// AssetSetResponse for unmarshaling of AssetSet.assets into AssetSet

// Unmarshals a marshaled AssetSet json into AssetSetResponse
func (asr *AssetSetResponse) UnmarshalJSON(b []byte) error {
	var assetMap map[string]*json.RawMessage

	// Partial unmarshal to map of json RawMessage
	err := json.Unmarshal(b, &assetMap)
	if err != nil {
		return err
	}

	err = asr.RawMessageToAssetSetResponse(assetMap)
	if err != nil {
		return err
	}

	return nil
}

func (asr *AssetSetResponse) RawMessageToAssetSetResponse(assetMap map[string]*json.RawMessage) error {

	newAssetMap := make(map[string]Asset)

	// For each item in asset map, unmarshal to appropriate type
	for key, rawMessage := range assetMap {

		var f interface{}

		err := json.Unmarshal(*rawMessage, &f)
		if err != nil {
			return err
		}

		fmap := f.(map[string]interface{})

		switch t := fmap["type"]; t {
		case "Cloud":

			var ca Cloud
			err := ca.InterfaceToCloud(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &ca

		case "ClusterManagement":

			var cm ClusterManagement
			err := cm.InterfaceToClusterManagement(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &cm

		case "Disk":

			var d Disk
			err := d.InterfaceToDisk(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &d

		case "Network":

			var nw Network
			err := nw.InterfaceToNetwork(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &nw

		case "Node":

			var n Node
			err := n.InterfaceToNode(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &n

		case "LoadBalancer":

			var lb LoadBalancer
			err := lb.InterfaceToLoadBalancer(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &lb

		case "Shared":

			var sa SharedAsset
			err := sa.InterfaceToSharedAsset(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &sa

		default:

			var a Any
			err := a.InterfaceToAny(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &a

		}
	}

	asr.Assets = newAssetMap

	return nil
}

func (asrr *AssetSetRangeResponse) UnmarshalJSON(b []byte) error {
	var assetMapList []map[string]*json.RawMessage

	// Partial unmarshal to map of json RawMessage
	err := json.Unmarshal(b, &assetMapList)
	if err != nil {
		return err
	}

	var assetSetList []*AssetSetResponse

	for _, rawm := range assetMapList {

		var asresp AssetSetResponse
		err = asresp.RawMessageToAssetSetResponse(rawm)
		if err != nil {
			return err
		}

		assetSetList = append(assetSetList, &asresp)

	}

	asrr.Assets = assetSetList

	return nil
}

// Extra decoding util functions, for clarity

// Creates an AssetProperties directly from map[string]interface{}
func toAssetProp(fproperties map[string]interface{}) AssetProperties {
	var properties AssetProperties

	if category, v := fproperties["category"].(string); v {
		properties.Category = category
	}
	if provider, v := fproperties["provider"].(string); v {
		properties.Provider = provider
	}
	if account, v := fproperties["account"].(string); v {
		properties.Account = account
	}
	if project, v := fproperties["project"].(string); v {
		properties.Project = project
	}
	if service, v := fproperties["service"].(string); v {
		properties.Service = service
	}
	if cluster, v := fproperties["cluster"].(string); v {
		properties.Cluster = cluster
	}
	if name, v := fproperties["name"].(string); v {
		properties.Name = name
	}
	if providerID, v := fproperties["providerID"].(string); v {
		properties.ProviderID = providerID
	}

	return properties

}

func toWindow(fproperties map[string]interface{}) Window {

	var start, end time.Time
	var err error

	if startStr, v := fproperties["start"].(string); v {
		start, err = time.Parse(time.RFC3339, startStr)

		if err != nil {
			log.Errorf("error parsing window start from string %s, setting as 0 time", startStr)
			start = time.Time{}
		}

	}

	if endStr, v := fproperties["end"].(string); v {
		end, err = time.Parse(time.RFC3339, endStr)

		if err != nil {
			log.Errorf("error parsing window end from string %s, setting as 0 time", endStr)
			end = time.Time{}
		}

	}

	return NewClosedWindow(start, end)

}

// Creates an Breakdown directly from map[string]interface{}
func toBreakdown(fproperties map[string]interface{}) Breakdown {
	var breakdown Breakdown

	if idle, v := fproperties["idle"].(float64); v {
		breakdown.Idle = idle
	}
	if other, v := fproperties["other"].(float64); v {
		breakdown.Other = other
	}
	if system, v := fproperties["system"].(float64); v {
		breakdown.System = system
	}
	if user, v := fproperties["user"].(float64); v {
		breakdown.User = user
	}

	return breakdown

}

// Not strictly nessesary, but cleans up the code and is a secondary check
// for correct types
func getTypedVal(itf interface{}) (interface{}, error) {
	switch itf := itf.(type) {
	case float64:
		return float64(itf), nil
	case string:
		return string(itf), nil
	default:
		unktype := reflect.ValueOf(itf)
		return nil, fmt.Errorf("Type %v is an invalid type", unktype)
	}
}
