package convert

import (
	"testing"

	volumetypes "github.com/docker/docker/api/types/volume"
	swarmapi "github.com/moby/swarmkit/v2/api"

	"gotest.tools/v3/assert"
	is "gotest.tools/v3/assert/cmp"
)

func TestTopologyFromGRPC(t *testing.T) {
	nilTopology := topologyFromGRPC(nil)
	assert.DeepEqual(t, nilTopology, volumetypes.Topology{})

	swarmTop := &swarmapi.Topology{
		Segments: map[string]string{"foo": "bar"},
	}

	top := topologyFromGRPC(swarmTop)
	assert.DeepEqual(t, top.Segments, swarmTop.Segments)
}

func TestCapacityRangeFromGRPC(t *testing.T) {
	nilCapacity := capacityRangeFromGRPC(nil)
	assert.Assert(t, is.Nil(nilCapacity))

	swarmZeroCapacity := &swarmapi.CapacityRange{}
	zeroCapacity := capacityRangeFromGRPC(swarmZeroCapacity)
	assert.Assert(t, zeroCapacity != nil)
	assert.Equal(t, zeroCapacity.RequiredBytes, int64(0))
	assert.Equal(t, zeroCapacity.LimitBytes, int64(0))

	swarmNonZeroCapacity := &swarmapi.CapacityRange{
		RequiredBytes: 1024,
		LimitBytes:    2048,
	}
	nonZeroCapacity := capacityRangeFromGRPC(swarmNonZeroCapacity)
	assert.Assert(t, nonZeroCapacity != nil)
	assert.Equal(t, nonZeroCapacity.RequiredBytes, int64(1024))
	assert.Equal(t, nonZeroCapacity.LimitBytes, int64(2048))
}

func TestVolumeAvailabilityFromGRPC(t *testing.T) {
	for _, tc := range []struct {
		name     string
		in       swarmapi.VolumeSpec_VolumeAvailability
		expected volumetypes.Availability
	}{
		{
			name:     "Active",
			in:       swarmapi.VolumeAvailabilityActive,
			expected: volumetypes.AvailabilityActive,
		}, {
			name:     "Pause",
			in:       swarmapi.VolumeAvailabilityPause,
			expected: volumetypes.AvailabilityPause,
		}, {
			name:     "Drain",
			in:       swarmapi.VolumeAvailabilityDrain,
			expected: volumetypes.AvailabilityDrain,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			actual := volumeAvailabilityFromGRPC(tc.in)
			assert.Equal(t, actual, tc.expected)
		})
	}
}

// TestAccessModeFromGRPC tests that the AccessMode type is correctly converted
func TestAccessModeFromGRPC(t *testing.T) {
	for _, tc := range []struct {
		name     string
		in       *swarmapi.VolumeAccessMode
		expected *volumetypes.AccessMode
	}{
		{
			name: "MountVolume",
			in: &swarmapi.VolumeAccessMode{
				Scope:   swarmapi.VolumeScopeSingleNode,
				Sharing: swarmapi.VolumeSharingNone,
				AccessType: &swarmapi.VolumeAccessMode_Mount{
					Mount: &swarmapi.VolumeAccessMode_MountVolume{
						FsType: "foo",
						// TODO(dperny): maybe don't convert this?
						MountFlags: []string{"one", "two"},
					},
				},
			},
			expected: &volumetypes.AccessMode{
				Scope:   volumetypes.ScopeSingleNode,
				Sharing: volumetypes.SharingNone,
				MountVolume: &volumetypes.TypeMount{
					FsType:     "foo",
					MountFlags: []string{"one", "two"},
				},
			},
		}, {
			name: "BlockVolume",
			in: &swarmapi.VolumeAccessMode{
				Scope:   swarmapi.VolumeScopeSingleNode,
				Sharing: swarmapi.VolumeSharingNone,
				AccessType: &swarmapi.VolumeAccessMode_Block{
					Block: &swarmapi.VolumeAccessMode_BlockVolume{},
				},
			},
			expected: &volumetypes.AccessMode{
				Scope:       volumetypes.ScopeSingleNode,
				Sharing:     volumetypes.SharingNone,
				BlockVolume: &volumetypes.TypeBlock{},
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			out := accessModeFromGRPC(tc.in)
			assert.DeepEqual(t, tc.expected, out)
		})
	}
}

// TestVolumeCreateToGRPC tests that a docker-typed VolumeCreateBody is
// correctly converted to a swarm-typed VolumeSpec.
func TestVolumeCreateToGRPC(t *testing.T) {
	volume := &volumetypes.CreateOptions{
		Driver:     "plug1",
		DriverOpts: map[string]string{"options": "yeah"},
		Labels:     map[string]string{"labeled": "yeah"},
		Name:       "volume1",
	}

	spec := &volumetypes.ClusterVolumeSpec{
		Group: "gronp",
		AccessMode: &volumetypes.AccessMode{
			Scope:   volumetypes.ScopeMultiNode,
			Sharing: volumetypes.SharingAll,
			MountVolume: &volumetypes.TypeMount{
				FsType:     "foo",
				MountFlags: []string{"one", "two"},
			},
		},
		Secrets: []volumetypes.Secret{
			{Key: "key1", Secret: "secret1"},
			{Key: "key2", Secret: "secret2"},
		},
		AccessibilityRequirements: &volumetypes.TopologyRequirement{
			Requisite: []volumetypes.Topology{
				{Segments: map[string]string{"top1": "yup"}},
				{Segments: map[string]string{"top2": "def"}},
				{Segments: map[string]string{"top3": "nah"}},
			},
			Preferred: []volumetypes.Topology{},
		},
		CapacityRange: &volumetypes.CapacityRange{
			RequiredBytes: 1,
			LimitBytes:    0,
		},
	}

	volume.ClusterVolumeSpec = spec

	swarmSpec := VolumeCreateToGRPC(volume)

	assert.Assert(t, swarmSpec != nil)
	expectedSwarmSpec := &swarmapi.VolumeSpec{
		Annotations: swarmapi.Annotations{
			Name: "volume1",
			Labels: map[string]string{
				"labeled": "yeah",
			},
		},
		Group: "gronp",
		Driver: &swarmapi.Driver{
			Name: "plug1",
			Options: map[string]string{
				"options": "yeah",
			},
		},
		AccessMode: &swarmapi.VolumeAccessMode{
			Scope:   swarmapi.VolumeScopeMultiNode,
			Sharing: swarmapi.VolumeSharingAll,
			AccessType: &swarmapi.VolumeAccessMode_Mount{
				Mount: &swarmapi.VolumeAccessMode_MountVolume{
					FsType:     "foo",
					MountFlags: []string{"one", "two"},
				},
			},
		},
		Secrets: []*swarmapi.VolumeSecret{
			{Key: "key1", Secret: "secret1"},
			{Key: "key2", Secret: "secret2"},
		},
		AccessibilityRequirements: &swarmapi.TopologyRequirement{
			Requisite: []*swarmapi.Topology{
				{Segments: map[string]string{"top1": "yup"}},
				{Segments: map[string]string{"top2": "def"}},
				{Segments: map[string]string{"top3": "nah"}},
			},
			Preferred: nil,
		},
		CapacityRange: &swarmapi.CapacityRange{
			RequiredBytes: 1,
			LimitBytes:    0,
		},
	}

	assert.DeepEqual(t, swarmSpec, expectedSwarmSpec)
}
