package db

import (
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestTimeRange_OverlapsExclusive(t *testing.T) {
	t.Run("no overlap", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 2)
		timeRangeB := newTimeRange(3, 4)

		overlap := timeRangeA.OverlapsExclusive(timeRangeB)
		require.False(t, overlap)
		overlap = timeRangeB.OverlapsExclusive(timeRangeA)
		require.False(t, overlap)
	})

	t.Run("equal", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 2)
		timeRangeB := newTimeRange(1, 2)

		overlap := timeRangeA.OverlapsExclusive(timeRangeB)
		require.True(t, overlap)
		overlap = timeRangeB.OverlapsExclusive(timeRangeA)
		require.True(t, overlap)
	})

	t.Run("surrounds", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 4)
		timeRangeB := newTimeRange(2, 3)

		overlap := timeRangeA.OverlapsExclusive(timeRangeB)
		require.True(t, overlap)
		overlap = timeRangeB.OverlapsExclusive(timeRangeA)
		require.True(t, overlap)
	})

	t.Run("partial", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 3)
		timeRangeB := newTimeRange(2, 4)

		overlap := timeRangeA.OverlapsExclusive(timeRangeB)
		require.True(t, overlap)
		overlap = timeRangeB.OverlapsExclusive(timeRangeA)
		require.True(t, overlap)
	})

	t.Run("adjacent", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 2)
		timeRangeB := newTimeRange(2, 3)

		overlap := timeRangeA.OverlapsExclusive(timeRangeB)
		require.False(t, overlap)
		overlap = timeRangeB.OverlapsExclusive(timeRangeA)
		require.False(t, overlap)
	})
}

func TestTimeRange_OverlapsInclusive(t *testing.T) {
	t.Run("no overlap", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 2)
		timeRangeB := newTimeRange(3, 4)

		overlap := timeRangeA.OverlapsInclusive(timeRangeB)
		require.False(t, overlap)
		overlap = timeRangeB.OverlapsInclusive(timeRangeA)
		require.False(t, overlap)
	})

	t.Run("equal", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 2)
		timeRangeB := newTimeRange(1, 2)

		overlap := timeRangeA.OverlapsInclusive(timeRangeB)
		require.True(t, overlap)
		overlap = timeRangeB.OverlapsInclusive(timeRangeA)
		require.True(t, overlap)
	})

	t.Run("surrounds", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 4)
		timeRangeB := newTimeRange(2, 3)

		overlap := timeRangeA.OverlapsInclusive(timeRangeB)
		require.True(t, overlap)
		overlap = timeRangeB.OverlapsInclusive(timeRangeA)
		require.True(t, overlap)
	})

	t.Run("partial", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 3)
		timeRangeB := newTimeRange(2, 4)

		overlap := timeRangeA.OverlapsInclusive(timeRangeB)
		require.True(t, overlap)
		overlap = timeRangeB.OverlapsInclusive(timeRangeA)
		require.True(t, overlap)
	})

	t.Run("adjacent", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 2)
		timeRangeB := newTimeRange(2, 3)

		overlap := timeRangeA.OverlapsInclusive(timeRangeB)
		require.True(t, overlap)
		overlap = timeRangeB.OverlapsInclusive(timeRangeA)
		require.True(t, overlap)
	})
}

func TestTimeRange_Merge(t *testing.T) {
	timeRangeA := newTimeRange(1, 2)
	timeRangeB := newTimeRange(2, 3)

	mergedTimeRange := timeRangeA.Merge(timeRangeB)
	require.Equal(t, newTimeRange(1, 3), mergedTimeRange)

	mergedTimeRange = timeRangeB.Merge(timeRangeA)
	require.Equal(t, newTimeRange(1, 3), mergedTimeRange)
}

func TestTimeRange_MergeIfOverlaps(t *testing.T) {
	t.Run("adjacent", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 2)
		timeRangeB := newTimeRange(2, 3)

		mergedTimeRanges := timeRangeA.MergeIfOverlaps(timeRangeB)
		require.Len(t, mergedTimeRanges, 1)
		require.Equal(t, newTimeRange(1, 3), mergedTimeRanges[0])

		mergedTimeRanges = timeRangeB.MergeIfOverlaps(timeRangeA)
		require.Len(t, mergedTimeRanges, 1)
		require.Equal(t, newTimeRange(1, 3), mergedTimeRanges[0])
	})

	t.Run("no overlap", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 2)
		timeRangeB := newTimeRange(3, 4)

		mergedTimeRanges := timeRangeA.MergeIfOverlaps(timeRangeB)
		require.Len(t, mergedTimeRanges, 2)
		require.Contains(t, mergedTimeRanges, timeRangeA)
		require.Contains(t, mergedTimeRanges, timeRangeB)

		mergedTimeRanges = timeRangeB.MergeIfOverlaps(timeRangeA)
		require.Len(t, mergedTimeRanges, 2)
		require.Contains(t, mergedTimeRanges, timeRangeA)
		require.Contains(t, mergedTimeRanges, timeRangeB)
	})

	t.Run("surrounds", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 4)
		timeRangeB := newTimeRange(2, 3)

		mergedTimeRanges := timeRangeA.MergeIfOverlaps(timeRangeB)
		require.Len(t, mergedTimeRanges, 1)
		require.Contains(t, mergedTimeRanges, newTimeRange(1, 4))

		mergedTimeRanges = timeRangeB.MergeIfOverlaps(timeRangeA)
		require.Len(t, mergedTimeRanges, 1)
		require.Contains(t, mergedTimeRanges, newTimeRange(1, 4))
	})
}

func TestTimeRange_RemoveOverlaps(t *testing.T) {
	t.Run("adjacent", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 2)
		timeRangeB := newTimeRange(2, 3)

		timeRanges := timeRangeA.RemoveOverlaps(timeRangeB)
		require.Len(t, timeRanges, 1)
		require.Contains(t, timeRanges, timeRangeA)

		timeRanges = timeRangeB.RemoveOverlaps(timeRangeA)
		require.Len(t, timeRanges, 1)
		require.Contains(t, timeRanges, timeRangeB)
	})

	t.Run("no overlap", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 2)
		timeRangeB := newTimeRange(3, 4)

		timeRanges := timeRangeA.RemoveOverlaps(timeRangeB)
		require.Len(t, timeRanges, 1)
		require.Contains(t, timeRanges, timeRangeA)

		timeRanges = timeRangeB.RemoveOverlaps(timeRangeA)
		require.Len(t, timeRanges, 1)
		require.Contains(t, timeRanges, timeRangeB)
	})

	t.Run("surrounds", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 4)
		timeRangeB := newTimeRange(2, 3)

		timeRanges := timeRangeA.RemoveOverlaps(timeRangeB)
		require.Len(t, timeRanges, 2)
		require.Contains(t, timeRanges, newTimeRange(1, 2))
		require.Contains(t, timeRanges, newTimeRange(3, 4))

		timeRanges = timeRangeB.RemoveOverlaps(timeRangeA)
		require.Empty(t, timeRanges, 1)
	})

	t.Run("partial", func(t *testing.T) {
		timeRangeA := newTimeRange(1, 3)
		timeRangeB := newTimeRange(2, 4)

		timeRanges := timeRangeA.RemoveOverlaps(timeRangeB)
		require.Len(t, timeRanges, 1)
		require.Contains(t, timeRanges, newTimeRange(1, 2))

		timeRanges = timeRangeB.RemoveOverlaps(timeRangeA)
		require.Len(t, timeRanges, 1)
		require.Contains(t, timeRanges, newTimeRange(3, 4))
	})
}

func newTimeRange(startDay, endDay int) TimeRange {
	return TimeRange{
		Start: time.Date(2020, time.January, startDay, 0, 0, 0, 0, time.UTC),
		End:   time.Date(2020, time.January, endDay, 0, 0, 0, 0, time.UTC),
	}
}
