package costmodel

import (
	"sort"
	"time"

	"github.com/kubecost/cost-model/pkg/kubecost"
)

// IntervalPoint describes a start or end of a window of time
// Currently, this used in PVC-pod relations to detect/calculate
// coefficients for PV cost when a PVC is shared between pods.
type IntervalPoint struct {
	Time      time.Time
	PointType string
	Key       podKey
}

// IntervalPoints describes a slice of IntervalPoint structs
type IntervalPoints []IntervalPoint

// Requisite functions for implementing sort.Sort for
// IntervalPointList
func (ips IntervalPoints) Len() int {
	return len(ips)
}

func (ips IntervalPoints) Less(i, j int) bool {
	if ips[i].Time.Equal(ips[j].Time) {
		return ips[i].PointType == "start" && ips[j].PointType == "end"
	}
	return ips[i].Time.Before(ips[j].Time)
}

func (ips IntervalPoints) Swap(i, j int) {
	ips[i], ips[j] = ips[j], ips[i]
}

// NewIntervalPoint creates and returns a new IntervalPoint instance with given parameters.
func NewIntervalPoint(time time.Time, pointType string, key podKey) IntervalPoint {
	return IntervalPoint{
		Time:      time,
		PointType: pointType,
		Key:       key,
	}
}

// CoefficientComponent is a representitive struct holding two fields which describe an interval
// as part of a single number cost coefficient calculation:
// 1. Proportion: The division of cost based on how many pods were running between those points
// 2. Time: The ratio of the time between those points to the total time that pod was running
type CoefficientComponent struct {
	Proportion float64
	Time       float64
}

// getIntervalPointFromWindows takes a map of podKeys to windows
// and returns a sorted list of IntervalPoints representing the
// starts and ends of all those windows.
func getIntervalPointsFromWindows(windows map[podKey]kubecost.Window) IntervalPoints {

	var intervals IntervalPoints

	for podKey, podInterval := range windows {

		start := NewIntervalPoint(*podInterval.Start(), "start", podKey)
		end := NewIntervalPoint(*podInterval.End(), "end", podKey)

		intervals = append(intervals, []IntervalPoint{start, end}...)

	}

	sort.Sort(intervals)

	return intervals

}

// getPVCCostCoefficients gets a coefficient which represents the scale
// factor that each PVC in a pvcIntervalMap and corresponding slice of
// IntervalPoints intervals uses to calculate a cost for that PVC's PV.
func getPVCCostCoefficients(intervals IntervalPoints, pvcIntervalMap map[podKey]kubecost.Window) map[podKey][]CoefficientComponent {

	pvcCostCoefficientMap := make(map[podKey][]CoefficientComponent)

	// pvcCostCoefficientMap is mutated in this function. The format is
	// such that the individual coefficient components are preserved for
	// testing purposes.

	activeKeys := map[podKey]struct{}{
		intervals[0].Key: struct{}{},
	}

	// For each interval i.e. for any time a pod-PVC relation ends or starts...
	for i := 1; i < len(intervals); i++ {

		// intervals will always have at least two IntervalPoints (one start/end)
		point := intervals[i]
		prevPoint := intervals[i-1]

		// If the current point happens at a later time than the previous point
		if !point.Time.Equal(prevPoint.Time) {
			for key := range activeKeys {
				if pvcIntervalMap[key].Duration().Minutes() != 0 {
					pvcCostCoefficientMap[key] = append(
						pvcCostCoefficientMap[key],
						CoefficientComponent{
							Time:       point.Time.Sub(prevPoint.Time).Minutes() / pvcIntervalMap[key].Duration().Minutes(),
							Proportion: 1.0 / float64(len(activeKeys)),
						},
					)
				}
			}
		}

		// If the point was a start, increment and track
		if point.PointType == "start" {
			activeKeys[point.Key] = struct{}{}
		}

		// If the point was an end, decrement and stop tracking
		if point.PointType == "end" {
			delete(activeKeys, point.Key)
		}

	}

	return pvcCostCoefficientMap
}

// getCoefficientFromComponents takes the components of a PVC-pod PV cost coefficient
// determined by getPVCCostCoefficient and gets the resulting single
// floating point coefficient.
func getCoefficientFromComponents(coefficientComponents []CoefficientComponent) float64 {

	coefficient := 0.0

	for i := range coefficientComponents {

		proportion := coefficientComponents[i].Proportion
		time := coefficientComponents[i].Time

		coefficient += proportion * time

	}

	return coefficient
}
