package sync

import (
	"testing"
	"time"

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

func TestDiffGroupedMkdir(t *testing.T) {
	d := diff{
		mkdir: []string{
			"foo",
			"foo/bar",
			"foo/bar/baz1",
			"foo/bar/baz2",
			"foo1",
			"a/b",
			"a/b/c/d/e/f",
		},
	}

	// Expect only leaf directories to be included.
	out := d.groupedMkdir()
	assert.Len(t, out, 1)
	assert.ElementsMatch(t, []string{
		"foo/bar/baz1",
		"foo/bar/baz2",
		"foo1",
		"a/b/c/d/e/f",
	}, out[0])
}

func TestDiffGroupedRmdir(t *testing.T) {
	d := diff{
		rmdir: []string{
			"a/b/c/d/e/f",
			"a/b/c/d/e",
			"a/b/c/d",
			"a/b/c",
			"a/b/e/f/g/h",
			"a/b/e/f/g",
			"a/b/e/f",
			"a/b/e",
			"a/b",
		},
	}

	out := d.groupedRmdir()
	assert.Len(t, out, 5)
	assert.ElementsMatch(t, []string{"a/b/c/d/e/f", "a/b/e/f/g/h"}, out[0])
	assert.ElementsMatch(t, []string{"a/b/c/d/e", "a/b/e/f/g"}, out[1])
	assert.ElementsMatch(t, []string{"a/b/c/d", "a/b/e/f"}, out[2])
	assert.ElementsMatch(t, []string{"a/b/c", "a/b/e"}, out[3])
	assert.ElementsMatch(t, []string{"a/b"}, out[4])
}

func TestDiffGroupedRmdirWithLeafsOnly(t *testing.T) {
	d := diff{
		rmdir: []string{
			"foo/bar/baz1",
			"foo/bar1",
			"foo/bar/baz2",
			"foo/bar2",
			"foo1",
			"foo2",
		},
	}

	// Expect all directories to be included.
	out := d.groupedRmdir()
	assert.Len(t, out, 1)
	assert.ElementsMatch(t, d.rmdir, out[0])
}

func TestDiffComputationForRemovedFiles(t *testing.T) {
	before := &SnapshotState{
		LocalToRemoteNames: map[string]string{
			"foo/a/b/c.py": "foo/a/b/c",
		},
		RemoteToLocalNames: map[string]string{
			"foo/a/b/c": "foo/a/b/c.py",
		},
	}
	after := &SnapshotState{}

	expected := diff{
		delete: []string{"foo/a/b/c"},
		rmdir:  []string{"foo", "foo/a", "foo/a/b"},
		mkdir:  []string{},
		put:    []string{},
	}
	assert.Equal(t, expected, computeDiff(after, before))
}

func TestDiffComputationWhenRemoteNameIsChanged(t *testing.T) {
	tick := time.Now()
	before := &SnapshotState{
		LocalToRemoteNames: map[string]string{
			"foo/a/b/c.py": "foo/a/b/c",
		},
		RemoteToLocalNames: map[string]string{
			"foo/a/b/c": "foo/a/b/c.py",
		},
		LastModifiedTimes: map[string]time.Time{
			"foo/a/b/c.py": tick,
		},
	}
	tick = tick.Add(time.Second)
	after := &SnapshotState{
		LocalToRemoteNames: map[string]string{
			"foo/a/b/c.py": "foo/a/b/c.py",
		},
		RemoteToLocalNames: map[string]string{
			"foo/a/b/c.py": "foo/a/b/c.py",
		},
		LastModifiedTimes: map[string]time.Time{
			"foo/a/b/c.py": tick,
		},
	}

	expected := diff{
		delete: []string{"foo/a/b/c"},
		rmdir:  []string{},
		mkdir:  []string{},
		put:    []string{"foo/a/b/c.py"},
	}
	assert.Equal(t, expected, computeDiff(after, before))
}

func TestDiffComputationForNewFiles(t *testing.T) {
	after := &SnapshotState{
		LocalToRemoteNames: map[string]string{
			"foo/a/b/c.py": "foo/a/b/c",
		},
		RemoteToLocalNames: map[string]string{
			"foo/a/b/c": "foo/a/b/c.py",
		},
		LastModifiedTimes: map[string]time.Time{
			"foo/a/b/c.py": time.Now(),
		},
	}

	expected := diff{
		delete: []string{},
		rmdir:  []string{},
		mkdir:  []string{"foo", "foo/a", "foo/a/b"},
		put:    []string{"foo/a/b/c.py"},
	}
	assert.Equal(t, expected, computeDiff(after, &SnapshotState{}))
}

func TestDiffComputationForUpdatedFiles(t *testing.T) {
	tick := time.Now()
	before := &SnapshotState{
		LocalToRemoteNames: map[string]string{
			"foo/a/b/c": "foo/a/b/c",
		},
		RemoteToLocalNames: map[string]string{
			"foo/a/b/c": "foo/a/b/c",
		},
		LastModifiedTimes: map[string]time.Time{
			"foo/a/b/c": tick,
		},
	}
	tick = tick.Add(time.Second)
	after := &SnapshotState{
		LocalToRemoteNames: map[string]string{
			"foo/a/b/c": "foo/a/b/c",
		},
		RemoteToLocalNames: map[string]string{
			"foo/a/b/c": "foo/a/b/c",
		},
		LastModifiedTimes: map[string]time.Time{
			"foo/a/b/c": tick,
		},
	}

	expected := diff{
		delete: []string{},
		rmdir:  []string{},
		mkdir:  []string{},
		put:    []string{"foo/a/b/c"},
	}
	assert.Equal(t, expected, computeDiff(after, before))
}