// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package timeutil

import (
	"reflect"
	"testing"
	"time"
)

func TestTimeutil_StartOfPreviousMonth(t *testing.T) {
	testCases := []struct {
		Input    time.Time
		Expected time.Time
	}{
		{
			Input:    time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
			Expected: time.Date(2019, 12, 1, 0, 0, 0, 0, time.UTC),
		},
		{
			Input:    time.Date(2020, 1, 15, 0, 0, 0, 0, time.UTC),
			Expected: time.Date(2019, 12, 1, 0, 0, 0, 0, time.UTC),
		},
		{
			Input:    time.Date(2020, 3, 31, 23, 59, 59, 999999999, time.UTC),
			Expected: time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC),
		},
	}

	for _, tc := range testCases {
		result := StartOfPreviousMonth(tc.Input)
		if !result.Equal(tc.Expected) {
			t.Errorf("start of month before %v is %v, got %v", tc.Input, tc.Expected, result)
		}
	}
}

func TestTimeutil_StartOfMonth(t *testing.T) {
	testCases := []struct {
		Input    time.Time
		Expected time.Time
	}{
		{
			Input:    time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
			Expected: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
		},
		{
			Input:    time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC),
			Expected: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
		},
		{
			Input:    time.Date(2020, 1, 1, 0, 0, 0, 1, time.UTC),
			Expected: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
		},
		{
			Input:    time.Date(2020, 1, 31, 23, 59, 59, 999999999, time.UTC),
			Expected: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
		},
		{
			Input:    time.Date(2020, 2, 28, 1, 2, 3, 4, time.UTC),
			Expected: time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC),
		},
	}

	for _, tc := range testCases {
		result := StartOfMonth(tc.Input)
		if !result.Equal(tc.Expected) {
			t.Errorf("start of %v is %v, expected %v", tc.Input, result, tc.Expected)
		}
	}
}

func TestTimeutil_IsMonthStart(t *testing.T) {
	testCases := []struct {
		input    time.Time
		expected bool
	}{
		{
			input:    time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
			expected: true,
		},
		{
			input:    time.Date(2020, 1, 1, 0, 0, 0, 1, time.UTC),
			expected: false,
		},
		{
			input:    time.Date(2020, 4, 5, 0, 0, 0, 0, time.UTC),
			expected: false,
		},
		{
			input:    time.Date(2020, 1, 31, 23, 59, 59, 999999999, time.UTC),
			expected: false,
		},
	}

	for _, tc := range testCases {
		result := IsMonthStart(tc.input)
		if result != tc.expected {
			t.Errorf("is %v the start of the month? expected %t, got %t", tc.input, tc.expected, result)
		}
	}
}

func TestTimeutil_EndOfMonth(t *testing.T) {
	testCases := []struct {
		Input    time.Time
		Expected time.Time
	}{
		{
			// The current behavior does not use the nanoseconds
			// because we didn't want to clutter the result of end-of-month reporting.
			Input:    time.Date(2020, 1, 31, 23, 59, 59, 0, time.UTC),
			Expected: time.Date(2020, 1, 31, 23, 59, 59, 0, time.UTC),
		},
		{
			Input:    time.Date(2020, 1, 31, 23, 59, 59, 999999999, time.UTC),
			Expected: time.Date(2020, 1, 31, 23, 59, 59, 0, time.UTC),
		},
		{
			Input:    time.Date(2020, 1, 15, 1, 2, 3, 4, time.UTC),
			Expected: time.Date(2020, 1, 31, 23, 59, 59, 0, time.UTC),
		},
		{
			// Leap year
			Input:    time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC),
			Expected: time.Date(2020, 2, 29, 23, 59, 59, 0, time.UTC),
		},
		{
			// non-leap year
			Input:    time.Date(2100, 2, 1, 0, 0, 0, 0, time.UTC),
			Expected: time.Date(2100, 2, 28, 23, 59, 59, 0, time.UTC),
		},
	}

	for _, tc := range testCases {
		result := EndOfMonth(tc.Input)
		if !result.Equal(tc.Expected) {
			t.Errorf("end of %v is %v, expected %v", tc.Input, result, tc.Expected)
		}
	}
}

func TestTimeutil_IsPreviousMonth(t *testing.T) {
	testCases := []struct {
		tInput       time.Time
		compareInput time.Time
		expected     bool
	}{
		{
			tInput:       time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
			compareInput: time.Date(2020, 1, 31, 0, 0, 0, 0, time.UTC),
			expected:     false,
		},
		{
			tInput:       time.Date(2019, 12, 31, 0, 0, 0, 0, time.UTC),
			compareInput: time.Date(2020, 1, 31, 0, 0, 0, 0, time.UTC),
			expected:     true,
		},
		{
			// leap year (false)
			tInput:       time.Date(2019, 12, 29, 10, 10, 10, 0, time.UTC),
			compareInput: time.Date(2020, 2, 29, 10, 10, 10, 0, time.UTC),
			expected:     false,
		},
		{
			// leap year (true)
			tInput:       time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
			compareInput: time.Date(2020, 2, 29, 10, 10, 10, 0, time.UTC),
			expected:     true,
		},
		{
			tInput:       time.Date(2018, 5, 5, 5, 0, 0, 0, time.UTC),
			compareInput: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
			expected:     false,
		},
		{
			// test normalization. want to make subtracting 1 month from 3/30/2020 doesn't yield 2/30/2020, normalized
			// to 3/1/2020
			tInput:       time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC),
			compareInput: time.Date(2020, 3, 30, 0, 0, 0, 0, time.UTC),
			expected:     true,
		},
	}

	for _, tc := range testCases {
		result := IsPreviousMonth(tc.tInput, tc.compareInput)
		if result != tc.expected {
			t.Errorf("%v in previous month to %v? expected %t, got %t", tc.tInput, tc.compareInput, tc.expected, result)
		}
	}
}

func TestTimeutil_IsCurrentMonth(t *testing.T) {
	now := time.Now()
	testCases := []struct {
		input    time.Time
		expected bool
	}{
		{
			input:    now,
			expected: true,
		},
		{
			input:    StartOfMonth(now).AddDate(0, 0, -1),
			expected: false,
		},
		{
			input:    EndOfMonth(now).AddDate(0, 0, -1),
			expected: true,
		},
		{
			input:    StartOfMonth(now).AddDate(-1, 0, 0),
			expected: false,
		},
	}

	for _, tc := range testCases {
		result := IsCurrentMonth(tc.input, now)
		if result != tc.expected {
			t.Errorf("invalid result. expected %t for %v", tc.expected, tc.input)
		}
	}
}

// TestTimeutil_IsCurrentDay checks if the test times equals the current day or not.
func TestTimeutil_IsCurrentDay(t *testing.T) {
	now := time.Now()
	testCases := []struct {
		input    time.Time
		expected bool
	}{
		{
			input:    now,
			expected: true,
		},
		{
			input:    StartOfDay(now).AddDate(0, 0, -1),
			expected: false,
		},
		{
			input:    StartOfDay(now).AddDate(-1, 0, 0),
			expected: false,
		},
		{
			input:    StartOfDay(now).Add(1 * time.Second),
			expected: true,
		},
		{
			input:    StartOfDay(now).Add(-1 * time.Second),
			expected: false,
		},
		{
			input:    StartOfDay(now).Add(86400), // a day is 86400 seconds
			expected: true,
		},
	}

	for _, tc := range testCases {
		result := IsCurrentDay(tc.input, now)
		if result != tc.expected {
			t.Errorf("invalid result. expected %t for %v", tc.expected, tc.input)
		}
	}
}

func TestTimeUtil_ContiguousMonths(t *testing.T) {
	testCases := []struct {
		input    []time.Time
		expected []time.Time
	}{
		{
			input: []time.Time{
				time.Date(2020, 4, 1, 0, 0, 0, 0, time.UTC),
				time.Date(2020, 3, 1, 0, 0, 0, 0, time.UTC),
				time.Date(2020, 2, 5, 0, 0, 0, 0, time.UTC),
				time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
			},
			expected: []time.Time{
				time.Date(2020, 4, 1, 0, 0, 0, 0, time.UTC),
				time.Date(2020, 3, 1, 0, 0, 0, 0, time.UTC),
				time.Date(2020, 2, 5, 0, 0, 0, 0, time.UTC),
			},
		},
		{
			input: []time.Time{
				time.Date(2020, 4, 1, 0, 0, 0, 0, time.UTC),
				time.Date(2020, 3, 1, 0, 0, 0, 0, time.UTC),
				time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC),
				time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
			},
			expected: []time.Time{
				time.Date(2020, 4, 1, 0, 0, 0, 0, time.UTC),
				time.Date(2020, 3, 1, 0, 0, 0, 0, time.UTC),
				time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC),
				time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
			},
		},
		{
			input: []time.Time{
				time.Date(2020, 4, 1, 0, 0, 0, 0, time.UTC),
			},
			expected: []time.Time{
				time.Date(2020, 4, 1, 0, 0, 0, 0, time.UTC),
			},
		},
		{
			input:    []time.Time{},
			expected: []time.Time{},
		},
		{
			input:    nil,
			expected: nil,
		},
		{
			input: []time.Time{
				time.Date(2020, 2, 2, 0, 0, 0, 0, time.UTC),
				time.Date(2020, 1, 15, 0, 0, 0, 0, time.UTC),
			},
			expected: []time.Time{
				time.Date(2020, 2, 2, 0, 0, 0, 0, time.UTC),
			},
		},
	}

	for _, tc := range testCases {
		result := GetMostRecentContiguousMonths(tc.input)

		if !reflect.DeepEqual(tc.expected, result) {
			t.Errorf("invalid contiguous segment returned. expected %v, got %v", tc.expected, result)
		}
	}
}

func TestTimeUtil_ParseTimeFromPath(t *testing.T) {
	testCases := []struct {
		input       string
		expectedOut time.Time
		expectError bool
	}{
		{
			input:       "719020800/1",
			expectedOut: time.Unix(719020800, 0).UTC(),
			expectError: false,
		},
		{
			input:       "1601415205/3",
			expectedOut: time.Unix(1601415205, 0).UTC(),
			expectError: false,
		},
		{
			input:       "baddata/3",
			expectedOut: time.Time{},
			expectError: true,
		},
	}

	for _, tc := range testCases {
		result, err := ParseTimeFromPath(tc.input)
		gotError := err != nil

		if result != tc.expectedOut {
			t.Errorf("bad timestamp on input %q. expected: %v got: %v", tc.input, tc.expectedOut, result)
		}
		if gotError != tc.expectError {
			t.Errorf("bad error status on input %q. expected error: %t, got error: %t", tc.input, tc.expectError, gotError)
		}
	}
}
