package costmodel

import (
	"reflect"
	"testing"
	"time"

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

func TestGetIntervalPointsFromWindows(t *testing.T) {
	cases := []struct {
		name           string
		pvcIntervalMap map[podKey]kubecost.Window
		expected       []IntervalPoint
	}{
		{
			name: "four pods w/ various overlaps",
			pvcIntervalMap: map[podKey]kubecost.Window{
				// Pod running from 8 am to 9 am
				podKey{
					Pod: "Pod1",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
				)),
				// Pod running from 8:30 am to 9 am
				podKey{
					Pod: "Pod2",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
				)),
				// Pod running from 8:45 am to 9 am
				podKey{
					Pod: "Pod3",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 45, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
				)),
				// Pod running from 8 am to 8:15 am
				podKey{
					Pod: "Pod4",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 8, 15, 0, 0, time.UTC),
				)),
			},
			expected: []IntervalPoint{
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod4"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 15, 0, 0, time.UTC), "end", podKey{Pod: "Pod4"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod2"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 45, 0, 0, time.UTC), "start", podKey{Pod: "Pod3"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod2"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod3"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
			},
		},
		{
			name: "two pods no overlap",
			pvcIntervalMap: map[podKey]kubecost.Window{
				// Pod running from 8 am to 8:30 am
				podKey{
					Pod: "Pod1",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
				)),
				// Pod running from 8:30 am to 9 am
				podKey{
					Pod: "Pod2",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
				)),
			},
			expected: []IntervalPoint{
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod2"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod2"}),
			},
		},
		{
			name: "two pods total overlap",
			pvcIntervalMap: map[podKey]kubecost.Window{
				// Pod running from 8:30 am to 9 am
				podKey{
					Pod: "Pod1",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
				)),
				// Pod running from 8:30 am to 9 am
				podKey{
					Pod: "Pod2",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
				)),
			},
			expected: []IntervalPoint{
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod2"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod2"}),
			},
		},
		{
			name: "one pod",
			pvcIntervalMap: map[podKey]kubecost.Window{
				// Pod running from 8 am to 9 am
				podKey{
					Pod: "Pod1",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
				)),
			},
			expected: []IntervalPoint{
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
			},
		},
	}

	for _, testCase := range cases {
		t.Run(testCase.name, func(t *testing.T) {

			result := getIntervalPointsFromWindows(testCase.pvcIntervalMap)

			if len(result) != len(testCase.expected) {
				t.Errorf("getIntervalPointsFromWindows test failed: %s: Got %+v but expected %+v", testCase.name, result, testCase.expected)
			}

			for i := range testCase.expected {

				// For correctness in terms of individual position of IntervalPoints, we only need to check the time/type.
				// Key is used in other associated calculations, so it must exist, but order does not matter if other sorting
				// logic is obeyed.
				if !testCase.expected[i].Time.Equal(result[i].Time) || testCase.expected[i].PointType != result[i].PointType {
					t.Errorf("getIntervalPointsFromWindows test failed: %s: Got point %s:%s but expected %s:%s", testCase.name, testCase.expected[i].PointType, testCase.expected[i].Time, result[i].PointType, result[i].Time)
				}

			}

		})
	}
}

func TestGetPVCCostCoefficients(t *testing.T) {
	cases := []struct {
		name           string
		pvcIntervalMap map[podKey]kubecost.Window
		intervals      []IntervalPoint
		expected       map[podKey][]CoefficientComponent
	}{
		{
			name: "four pods w/ various overlaps",
			pvcIntervalMap: map[podKey]kubecost.Window{
				// Pod running from 8 am to 9 am
				podKey{
					Pod: "Pod1",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
				)),
				// Pod running from 8:30 am to 9 am
				podKey{
					Pod: "Pod2",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
				)),
				// Pod running from 8:45 am to 9 am
				podKey{
					Pod: "Pod3",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 45, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
				)),
				// Pod running from 8 am to 8:15 am
				podKey{
					Pod: "Pod4",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 8, 15, 0, 0, time.UTC),
				)),
			},
			intervals: []IntervalPoint{
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod4"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 15, 0, 0, time.UTC), "end", podKey{Pod: "Pod4"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod2"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 45, 0, 0, time.UTC), "start", podKey{Pod: "Pod3"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod2"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod3"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
			},
			expected: map[podKey][]CoefficientComponent{
				podKey{
					Pod: "Pod1",
				}: []CoefficientComponent{
					CoefficientComponent{0.5, 0.25},
					CoefficientComponent{1, 0.25},
					CoefficientComponent{0.5, 0.25},
					CoefficientComponent{1.0 / 3.0, 0.25},
				},
				podKey{
					Pod: "Pod2",
				}: []CoefficientComponent{
					CoefficientComponent{0.5, 0.50},
					CoefficientComponent{1.0 / 3.0, 0.50},
				},
				podKey{
					Pod: "Pod3",
				}: []CoefficientComponent{
					CoefficientComponent{1.0 / 3.0, 1.0},
				},
				podKey{
					Pod: "Pod4",
				}: []CoefficientComponent{
					CoefficientComponent{0.5, 1.0},
				},
			},
		},
		{
			name: "two pods no overlap",
			pvcIntervalMap: map[podKey]kubecost.Window{
				// Pod running from 8 am to 8:30 am
				podKey{
					Pod: "Pod1",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
				)),
				// Pod running from 8:30 am to 9 am
				podKey{
					Pod: "Pod2",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
				)),
			},
			intervals: []IntervalPoint{
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod2"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod2"}),
			},
			expected: map[podKey][]CoefficientComponent{
				podKey{
					Pod: "Pod1",
				}: []CoefficientComponent{
					CoefficientComponent{1.0, 1.0},
				},
				podKey{
					Pod: "Pod2",
				}: []CoefficientComponent{
					CoefficientComponent{1.0, 1.0},
				},
			},
		},
		{
			name: "two pods total overlap",
			pvcIntervalMap: map[podKey]kubecost.Window{
				// Pod running from 8:30 am to 9 am
				podKey{
					Pod: "Pod1",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
				)),
				// Pod running from 8:30 am to 9 am
				podKey{
					Pod: "Pod2",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
				)),
			},
			intervals: []IntervalPoint{
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 30, 0, 0, time.UTC), "start", podKey{Pod: "Pod2"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod2"}),
			},
			expected: map[podKey][]CoefficientComponent{
				podKey{
					Pod: "Pod1",
				}: []CoefficientComponent{
					CoefficientComponent{0.5, 1.0},
				},
				podKey{
					Pod: "Pod2",
				}: []CoefficientComponent{
					CoefficientComponent{0.5, 1.0},
				},
			},
		},
		{
			name: "one pod",
			pvcIntervalMap: map[podKey]kubecost.Window{
				// Pod running from 8 am to 9 am
				podKey{
					Pod: "Pod1",
				}: kubecost.Window(kubecost.NewClosedWindow(
					time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC),
					time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC),
				)),
			},
			intervals: []IntervalPoint{
				NewIntervalPoint(time.Date(2021, 2, 19, 8, 0, 0, 0, time.UTC), "start", podKey{Pod: "Pod1"}),
				NewIntervalPoint(time.Date(2021, 2, 19, 9, 0, 0, 0, time.UTC), "end", podKey{Pod: "Pod1"}),
			},
			expected: map[podKey][]CoefficientComponent{
				podKey{
					Pod: "Pod1",
				}: []CoefficientComponent{
					CoefficientComponent{1.0, 1.0},
				},
			},
		},
	}

	for _, testCase := range cases {
		t.Run(testCase.name, func(t *testing.T) {
			result := getPVCCostCoefficients(testCase.intervals, testCase.pvcIntervalMap)

			if !reflect.DeepEqual(result, testCase.expected) {
				t.Errorf("getPVCCostCoefficients test failed: %s: Got %+v but expected %+v", testCase.name, result, testCase.expected)
			}
		})
	}
}
