package main

import (
	"context"
	"isle/daemon"
	"isle/daemon/daecommon"
	"isle/toolkit"
	"testing"

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

func TestStorageAllocationAdd(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name            string
		args            []string
		setExpectations func(*daemon.MockRPC)
		wantAlloc       daecommon.ConfigStorageAllocation
	}{
		{
			name: "success/no ports",
			args: []string{
				"--data-path", "foo", "--meta-path=bar", "--capacity", "1",
			},
			wantAlloc: daecommon.ConfigStorageAllocation{
				DataPath: "foo",
				MetaPath: "bar",
				Capacity: 1,
			},
		},
		{
			name: "success/all ports",
			args: []string{
				"--data-path", "foo",
				"--meta-path=bar",
				"--capacity", "1",
				"--s3-api-port", "1000",
				"--rpc-port=2000",
				"--admin-port", "3000",
			},
			wantAlloc: daecommon.ConfigStorageAllocation{
				DataPath:  "foo",
				MetaPath:  "bar",
				Capacity:  1,
				S3APIPort: 1000,
				RPCPort:   2000,
				AdminPort: 3000,
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			var (
				h      = newRunHarness(t)
				config = daecommon.NewNetworkConfig(nil)
			)

			h.daemonRPC.
				On("GetConfig", toolkit.MockArg[context.Context]()).
				Return(config, nil).
				Once()

			config.Storage.Allocations = append(
				config.Storage.Allocations, test.wantAlloc,
			)

			h.daemonRPC.
				On(
					"SetConfig",
					toolkit.MockArg[context.Context](),
					config,
				).
				Return(nil).
				Once()

			args := []string{"storage", "add-allocation"}
			args = append(args, test.args...)
			assert.NoError(t, h.run(t, args...))
		})
	}
}

func TestStorageAllocationList(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name   string
		allocs []daecommon.ConfigStorageAllocation
		want   any
	}{
		{
			name:   "empty",
			allocs: nil,
			want:   []any{},
		},
		{
			// results should get sorted according to RPCPort, with index
			// reflecting that order.
			name: "success",
			allocs: []daecommon.ConfigStorageAllocation{
				{
					DataPath:  "b",
					MetaPath:  "B",
					Capacity:  2,
					S3APIPort: 2000,
					RPCPort:   2001,
					AdminPort: 2002,
				},
				{
					DataPath:  "a",
					MetaPath:  "A",
					Capacity:  1,
					S3APIPort: 1000,
					RPCPort:   1001,
					AdminPort: 1002,
				},
			},
			want: []map[string]any{
				{
					"index":       0,
					"data_path":   "a",
					"meta_path":   "A",
					"capacity":    1,
					"s3_api_port": 1000,
					"rpc_port":    1001,
					"admin_port":  1002,
				},
				{
					"index":       1,
					"data_path":   "b",
					"meta_path":   "B",
					"capacity":    2,
					"s3_api_port": 2000,
					"rpc_port":    2001,
					"admin_port":  2002,
				},
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			var (
				h      = newRunHarness(t)
				config daecommon.NetworkConfig
			)

			config.Storage.Allocations = test.allocs

			h.daemonRPC.
				On("GetConfig", toolkit.MockArg[context.Context]()).
				Return(config, nil).
				Once()

			h.runAssertStdout(t, test.want, "storage", "list-allocations")
		})
	}
}

func TestStorageAllocationRemove(t *testing.T) {
	t.Parallel()

	config := func(rpcPorts ...int) daecommon.NetworkConfig {
		return daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) {
			for _, rpcPort := range rpcPorts {
				c.Storage.Allocations = append(
					c.Storage.Allocations,
					daecommon.ConfigStorageAllocation{RPCPort: rpcPort},
				)
			}
		})
	}

	tests := []struct {
		name            string
		args            []string
		setExpectations func(*daemon.MockRPC)
		wantErr         string
	}{
		{
			name:    "error/no indexes",
			args:    nil,
			wantErr: "At least one --index must be specified",
		},
		{
			name: "error/unknown index",
			args: []string{"--index", "1"},
			setExpectations: func(daemonRPC *daemon.MockRPC) {
				daemonRPC.
					On("GetConfig", toolkit.MockArg[context.Context]()).
					Return(config(1000), nil).
					Once()
			},
			wantErr: "Index 1 not found",
		},
		{
			name: "success/remove single",
			args: []string{"--index", "0"},
			setExpectations: func(daemonRPC *daemon.MockRPC) {
				config := config(1000, 2000)

				daemonRPC.
					On("GetConfig", toolkit.MockArg[context.Context]()).
					Return(config, nil).
					Once()

				config.Storage.Allocations = config.Storage.Allocations[1:]

				daemonRPC.
					On(
						"SetConfig",
						toolkit.MockArg[context.Context](),
						config,
					).
					Return(nil).
					Once()
			},
		},
		{
			name: "success/remove multiple",
			args: []string{"--index", "0", "--index", "2"},
			setExpectations: func(daemonRPC *daemon.MockRPC) {
				config := config(1000, 2000, 3000)

				daemonRPC.
					On("GetConfig", toolkit.MockArg[context.Context]()).
					Return(config, nil).
					Once()

				config.Storage.Allocations = config.Storage.Allocations[1:2]

				daemonRPC.
					On(
						"SetConfig",
						toolkit.MockArg[context.Context](),
						config,
					).
					Return(nil).
					Once()
			},
		},
		{
			name: "success/remove all",
			args: []string{"--index", "0", "--index", "1"},
			setExpectations: func(daemonRPC *daemon.MockRPC) {
				config := config(1000, 2000)

				daemonRPC.
					On("GetConfig", toolkit.MockArg[context.Context]()).
					Return(config, nil).
					Once()

				config.Storage.Allocations = config.Storage.Allocations[:0]

				daemonRPC.
					On(
						"SetConfig",
						toolkit.MockArg[context.Context](),
						config,
					).
					Return(nil).
					Once()
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			h := newRunHarness(t)

			if test.setExpectations != nil {
				test.setExpectations(h.daemonRPC)
			}

			args := []string{"storage", "remove-allocation"}
			args = append(args, test.args...)
			err := h.run(t, args...)

			if test.wantErr == "" {
				assert.NoError(t, err)
			} else {
				assert.Contains(t, err.Error(), test.wantErr)
			}
		})
	}
}