//go:build !nautilus
// +build !nautilus

package admin

import (
	"errors"
	"sort"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/ceph/go-ceph/internal/commands"
)

var ssList1 = `
{
    "4": {
        "name": "rbd/",
        "schedule": [
            {
                "interval": "70m",
                "start_time": null
            },
            {
                "interval": "30m",
                "start_time": null
            }
        ]
    },
    "4//106ff127efdc": {
        "name": "rbd/jumpy",
        "schedule": [
            {
                "interval": "99m",
                "start_time": null
            }
        ]
    }
}
`

var ssList2 = `
{
    "4": {
        "name": "rbd/",
        "schedule": [
            {
                "interval": "80m",
                "start_time": null
            }
        ]
    },
    "4//104f1d296736": {
        "name": "rbd/baz",
        "schedule": [
            {
                "interval": "1d",
                "start_time": "14:00:00-05:00"
            }
        ]
    }
}
`

var sStatus1 = `
{
    "scheduled_images": []
}
`
var sStatus2 = `
{
    "scheduled_images": [
        {
            "image": "rbd/foo",
            "schedule_time": "2021-03-02 16:30:00"
        }
    ]
}
`

var sStatus3 = `
{
    "scheduled_images": [
        {
            "image": "rbd/bar",
            "schedule_time": "2021-03-02 16:00:00"
        },
        {
            "image": "rbd/foo",
            "schedule_time": "2021-03-02 16:30:00"
        }
    ]
}
`

func TestParseMirrorSnapshotScheduleList(t *testing.T) {
	t.Run("list1", func(t *testing.T) {
		r := commands.NewResponse([]byte(ssList1), "", nil)
		l, err := parseMirrorSnapshotScheduleList(r)
		assert.NoError(t, err)
		if assert.Len(t, l, 2) {
			s1 := l[0]
			s2 := l[1]
			if s1.Name != "rbd/" {
				// just swap them.  it shouldn't matter to the test if the map has
				// changed the order.
				s1, s2 = s2, s1
			}
			assert.Equal(t, "rbd/", s1.Name)
			assert.Equal(t, "4", s1.LevelSpecID)
			if assert.Len(t, s1.Schedule, 2) {
				assert.EqualValues(t, "70m", s1.Schedule[0].Interval)
				assert.EqualValues(t, "", s1.Schedule[0].StartTime)
				assert.EqualValues(t, "30m", s1.Schedule[1].Interval)
				assert.EqualValues(t, "", s1.Schedule[1].StartTime)
			}

			assert.Equal(t, "rbd/jumpy", s2.Name)
			assert.Equal(t, "4//106ff127efdc", s2.LevelSpecID)
			if assert.Len(t, s2.Schedule, 1) {
				assert.EqualValues(t, "99m", s2.Schedule[0].Interval)
				assert.EqualValues(t, "", s2.Schedule[0].StartTime)
			}
		}
	})
	t.Run("list2", func(t *testing.T) {
		r := commands.NewResponse([]byte(ssList2), "", nil)
		l, err := parseMirrorSnapshotScheduleList(r)
		assert.NoError(t, err)
		if assert.Len(t, l, 2) {
			s1 := l[0]
			s2 := l[1]
			if s1.Name != "rbd/" {
				// just swap them.  it shouldn't matter to the test if the map has
				// changed the order.
				s1, s2 = s2, s1
			}
			assert.Equal(t, "rbd/", s1.Name)
			assert.Equal(t, "4", s1.LevelSpecID)
			if assert.Len(t, s1.Schedule, 1) {
				assert.EqualValues(t, "80m", s1.Schedule[0].Interval)
				assert.EqualValues(t, "", s1.Schedule[0].StartTime)
			}

			assert.Equal(t, "rbd/baz", s2.Name)
			assert.Equal(t, "4//104f1d296736", s2.LevelSpecID)
			if assert.Len(t, s2.Schedule, 1) {
				assert.EqualValues(t, "1d", s2.Schedule[0].Interval)
				assert.EqualValues(t, "14:00:00-05:00", s2.Schedule[0].StartTime)
			}
		}
	})
	t.Run("empty", func(t *testing.T) {
		r := commands.NewResponse([]byte("{}"), "", nil)
		l, err := parseMirrorSnapshotScheduleList(r)
		assert.NoError(t, err)
		assert.Len(t, l, 0)
	})
	t.Run("error", func(t *testing.T) {
		r := commands.NewResponse([]byte{}, "", errors.New("yikes"))
		l, err := parseMirrorSnapshotScheduleList(r)
		assert.Error(t, err)
		assert.Len(t, l, 0)
	})
}

func TestParseMirrorSnapshotScheduleStatus(t *testing.T) {
	t.Run("status1", func(t *testing.T) {
		r := commands.NewResponse([]byte(sStatus1), "", nil)
		s, err := parseMirrorSnapshotScheduleStatus(r)
		assert.NoError(t, err)
		assert.Len(t, s, 0)
	})
	t.Run("status2", func(t *testing.T) {
		r := commands.NewResponse([]byte(sStatus2), "", nil)
		s, err := parseMirrorSnapshotScheduleStatus(r)
		assert.NoError(t, err)
		if assert.Len(t, s, 1) {
			assert.Equal(t, "rbd/foo", s[0].Image)
			assert.Contains(t, s[0].ScheduleTime, "16:30")
		}
	})
	t.Run("status3", func(t *testing.T) {
		r := commands.NewResponse([]byte(sStatus3), "", nil)
		s, err := parseMirrorSnapshotScheduleStatus(r)
		assert.NoError(t, err)
		if assert.Len(t, s, 2) {
			assert.Equal(t, "rbd/bar", s[0].Image)
			assert.Contains(t, s[0].ScheduleTime, "16:00")
			assert.Equal(t, "rbd/foo", s[1].Image)
			assert.Contains(t, s[1].ScheduleTime, "16:30")
		}
	})
	t.Run("error", func(t *testing.T) {
		r := commands.NewResponse([]byte{}, "", errors.New("zrkk"))
		s, err := parseMirrorSnapshotScheduleStatus(r)
		assert.Error(t, err)
		assert.Len(t, s, 0)
	})
}

func TestMirrorSnapshotScheduleAddRemove(t *testing.T) {
	ensureDefaultPool(t)
	ra := getAdmin(t)
	scheduler := ra.MirrorSnashotSchedule()
	t.Run("noStartTime", func(t *testing.T) {
		err := scheduler.Add(NewLevelSpec(defaultPoolName, "", ""), Interval("1d"), NoStartTime)
		assert.NoError(t, err)
		err = scheduler.Remove(NewLevelSpec(defaultPoolName, "", ""), Interval("1d"), NoStartTime)
		assert.NoError(t, err)
	})
	t.Run("startTime", func(t *testing.T) {
		stime := StartTime("12:00:00")
		err := scheduler.Add(NewLevelSpec(defaultPoolName, "", ""), Interval("1d"), stime)
		assert.NoError(t, err)
		err = scheduler.Remove(NewLevelSpec(defaultPoolName, "", ""), Interval("1d"), stime)
		assert.NoError(t, err)
	})
	t.Run("badStartTime", func(t *testing.T) {
		stime := StartTime("henry")
		err := scheduler.Add(NewLevelSpec(defaultPoolName, "", ""), Interval("1d"), stime)
		assert.Error(t, err)
	})
}

func TestMirrorSnapshotScheduleList(t *testing.T) {
	ensureDefaultPool(t)
	ra := getAdmin(t)
	// assume a pool of "rbd" exists?
	scheduler := ra.MirrorSnashotSchedule()
	err := scheduler.Add(NewLevelSpec(defaultPoolName, "", ""), Interval("1d"), NoStartTime)
	assert.NoError(t, err)
	defer func() {
		err = scheduler.Remove(NewLevelSpec(defaultPoolName, "", ""), Interval("1d"), NoStartTime)
		assert.NoError(t, err)
	}()
	slist, err := scheduler.List(NewLevelSpec(defaultPoolName, "", ""))
	assert.NoError(t, err)
	if assert.Len(t, slist, 1) {
		assert.Equal(t, "rbd/", slist[0].Name)
		if assert.Len(t, slist[0].Schedule, 1) {
			assert.Equal(t, Interval("1d"), slist[0].Schedule[0].Interval)
		}
	}

	err = scheduler.Add(NewLevelSpec(defaultPoolName, "", ""), Interval("8h"), NoStartTime)
	assert.NoError(t, err)
	defer func() {
		err = scheduler.Remove(NewLevelSpec(defaultPoolName, "", ""), Interval("8h"), NoStartTime)
		assert.NoError(t, err)
	}()
	slist, err = scheduler.List(NewLevelSpec(defaultPoolName, "", ""))
	assert.NoError(t, err)
	if assert.Len(t, slist, 1) {
		assert.Equal(t, "rbd/", slist[0].Name)
		if assert.Len(t, slist[0].Schedule, 2) {
			// ceph doesn't return the list in a "stable" order so we just
			// take the lazy approach and sort by the interval value
			sched := slist[0].Schedule
			sort.Slice(sched, func(i, j int) bool {
				return sched[i].Interval < sched[j].Interval
			})
			assert.Equal(t, Interval("1d"), sched[0].Interval)
			assert.Equal(t, Interval("8h"), sched[1].Interval)
		}
	}
}
