// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0

package sdk_test

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"

	"github.com/opentracing/opentracing-go/mocktracer"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	sdk "github.com/mainflux/mainflux/sdk/go"
	"github.com/mainflux/mainflux/things"
	httpapi "github.com/mainflux/mainflux/things/api/things/http"
	"github.com/mainflux/mainflux/things/mocks"
)

const (
	contentType = "application/senml+json"
	email       = "user@example.com"
	otherEmail  = "other_user@example.com"
	token       = "token"
	otherToken  = "other_token"
	wrongValue  = "wrong_value"
	badID       = "999"
	emptyValue  = ""

	keyPrefix = "123e4567-e89b-12d3-a456-"
)

var (
	metadata   = map[string]interface{}{"meta": "data"}
	metadata2  = map[string]interface{}{"meta": "data2"}
	thing      = sdk.Thing{ID: "1", Name: "test_device", Metadata: metadata}
	emptyThing = sdk.Thing{}
)

func newThingsService(tokens map[string]string) things.Service {
	auth := mocks.NewAuthService(tokens)
	conns := make(chan mocks.Connection)
	thingsRepo := mocks.NewThingRepository(conns)
	channelsRepo := mocks.NewChannelRepository(thingsRepo, conns)
	chanCache := mocks.NewChannelCache()
	thingCache := mocks.NewThingCache()
	idp := mocks.NewIdentityProvider()

	return things.New(auth, thingsRepo, channelsRepo, chanCache, thingCache, idp)
}

func newThingsServer(svc things.Service) *httptest.Server {
	mux := httpapi.MakeHandler(mocktracer.New(), svc)
	return httptest.NewServer(mux)
}

func TestCreateThing(t *testing.T) {
	svc := newThingsService(map[string]string{token: email})
	ts := newThingsServer(svc)
	defer ts.Close()

	sdkConf := sdk.Config{
		BaseURL:           ts.URL,
		UsersPrefix:       "",
		ThingsPrefix:      "",
		HTTPAdapterPrefix: "",
		MsgContentType:    contentType,
		TLSVerification:   false,
	}

	mainfluxSDK := sdk.NewSDK(sdkConf)

	cases := []struct {
		desc     string
		thing    sdk.Thing
		token    string
		err      error
		location string
	}{
		{
			desc:     "create new thing",
			thing:    thing,
			token:    token,
			err:      nil,
			location: "1",
		},
		{
			desc:     "create new empty thing",
			thing:    emptyThing,
			token:    token,
			err:      nil,
			location: "2",
		},
		{
			desc:     "create new thing with empty token",
			thing:    thing,
			token:    "",
			err:      createError(sdk.ErrFailedCreation, http.StatusForbidden),
			location: "",
		},
		{
			desc:     "create new thing with invalid token",
			thing:    thing,
			token:    wrongValue,
			err:      createError(sdk.ErrFailedCreation, http.StatusForbidden),
			location: "",
		},
	}
	for _, tc := range cases {
		loc, err := mainfluxSDK.CreateThing(tc.thing, tc.token)

		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
		assert.Equal(t, tc.location, loc, fmt.Sprintf("%s: expected location %s got %s", tc.desc, tc.location, loc))
	}
}

func TestCreateThings(t *testing.T) {
	svc := newThingsService(map[string]string{token: email})
	ts := newThingsServer(svc)
	defer ts.Close()

	sdkConf := sdk.Config{
		BaseURL:           ts.URL,
		UsersPrefix:       "",
		ThingsPrefix:      "",
		HTTPAdapterPrefix: "",
		MsgContentType:    contentType,
		TLSVerification:   false,
	}

	mainfluxSDK := sdk.NewSDK(sdkConf)

	things := []sdk.Thing{
		sdk.Thing{ID: "1", Name: "1", Key: "1"},
		sdk.Thing{ID: "2", Name: "2", Key: "2"},
	}

	cases := []struct {
		desc   string
		things []sdk.Thing
		token  string
		err    error
		res    []sdk.Thing
	}{
		{
			desc:   "create new things",
			things: things,
			token:  token,
			err:    nil,
			res:    things,
		},
		{
			desc:   "create new things with empty things",
			things: []sdk.Thing{},
			token:  token,
			err:    createError(sdk.ErrFailedCreation, http.StatusBadRequest),
			res:    []sdk.Thing{},
		},
		{
			desc:   "create new thing with empty token",
			things: things,
			token:  "",
			err:    createError(sdk.ErrFailedCreation, http.StatusForbidden),
			res:    []sdk.Thing{},
		},
		{
			desc:   "create new thing with invalid token",
			things: things,
			token:  wrongValue,
			err:    createError(sdk.ErrFailedCreation, http.StatusForbidden),
			res:    []sdk.Thing{},
		},
	}
	for _, tc := range cases {
		res, err := mainfluxSDK.CreateThings(tc.things, tc.token)
		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))

		for idx := range tc.res {
			assert.Equal(t, tc.res[idx].ID, res[idx].ID, fmt.Sprintf("%s: expected response ID %s got %s", tc.desc, tc.res[idx].ID, res[idx].ID))
		}
	}
}

func TestThing(t *testing.T) {
	svc := newThingsService(map[string]string{token: email})
	ts := newThingsServer(svc)
	defer ts.Close()

	sdkConf := sdk.Config{
		BaseURL:           ts.URL,
		UsersPrefix:       "",
		ThingsPrefix:      "",
		HTTPAdapterPrefix: "",
		MsgContentType:    contentType,
		TLSVerification:   false,
	}

	mainfluxSDK := sdk.NewSDK(sdkConf)
	id, err := mainfluxSDK.CreateThing(thing, token)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
	thing.Key = fmt.Sprintf("%s%012d", keyPrefix, 2)

	cases := []struct {
		desc     string
		thID     string
		token    string
		err      error
		response sdk.Thing
	}{
		{
			desc:     "get existing thing",
			thID:     id,
			token:    token,
			err:      nil,
			response: thing,
		},
		{
			desc:     "get non-existent thing",
			thID:     "43",
			token:    token,
			err:      createError(sdk.ErrFailedFetch, http.StatusNotFound),
			response: sdk.Thing{},
		},
		{
			desc:     "get thing with invalid token",
			thID:     id,
			token:    wrongValue,
			err:      createError(sdk.ErrFailedFetch, http.StatusForbidden),
			response: sdk.Thing{},
		},
	}

	for _, tc := range cases {
		respTh, err := mainfluxSDK.Thing(tc.thID, tc.token)

		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
		assert.Equal(t, tc.response, respTh, fmt.Sprintf("%s: expected response thing %s, got %s", tc.desc, tc.response, respTh))
	}
}

func TestThings(t *testing.T) {
	svc := newThingsService(map[string]string{token: email})
	ts := newThingsServer(svc)
	defer ts.Close()
	sdkConf := sdk.Config{
		BaseURL:           ts.URL,
		UsersPrefix:       "",
		ThingsPrefix:      "",
		HTTPAdapterPrefix: "",
		MsgContentType:    contentType,
		TLSVerification:   false,
	}
	var things []sdk.Thing

	mainfluxSDK := sdk.NewSDK(sdkConf)
	for i := 1; i < 101; i++ {

		th := sdk.Thing{ID: strconv.Itoa(i), Name: "test_device", Metadata: metadata}
		mainfluxSDK.CreateThing(th, token)
		th.Key = fmt.Sprintf("%s%012d", keyPrefix, 2*i)
		things = append(things, th)
	}

	cases := []struct {
		desc     string
		token    string
		offset   uint64
		limit    uint64
		err      error
		response []sdk.Thing
		name     string
	}{
		{
			desc:     "get a list of things",
			token:    token,
			offset:   0,
			limit:    5,
			err:      nil,
			response: things[0:5],
		},
		{
			desc:     "get a list of things with invalid token",
			token:    wrongValue,
			offset:   0,
			limit:    5,
			err:      createError(sdk.ErrFailedFetch, http.StatusForbidden),
			response: nil,
		},
		{
			desc:     "get a list of things with empty token",
			token:    "",
			offset:   0,
			limit:    5,
			err:      createError(sdk.ErrFailedFetch, http.StatusForbidden),
			response: nil,
		},
		{
			desc:     "get a list of things with zero limit",
			token:    token,
			offset:   0,
			limit:    0,
			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
			response: nil,
		},
		{
			desc:     "get a list of things with limit greater than max",
			token:    token,
			offset:   0,
			limit:    110,
			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
			response: nil,
		},
		{
			desc:     "get a list of things with offset greater than max",
			token:    token,
			offset:   110,
			limit:    5,
			err:      nil,
			response: []sdk.Thing{},
		},
		{
			desc:     "get a list of things with invalid args (zero limit) and invalid token",
			token:    wrongValue,
			offset:   0,
			limit:    0,
			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
			response: nil,
		},
	}
	for _, tc := range cases {
		page, err := mainfluxSDK.Things(tc.token, tc.offset, tc.limit, tc.name)
		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
		assert.Equal(t, tc.response, page.Things, fmt.Sprintf("%s: expected response channel %s, got %s", tc.desc, tc.response, page.Things))
	}
}

func TestThingsByChannel(t *testing.T) {
	svc := newThingsService(map[string]string{token: email})
	ts := newThingsServer(svc)
	defer ts.Close()
	sdkConf := sdk.Config{
		BaseURL:           ts.URL,
		UsersPrefix:       "",
		ThingsPrefix:      "",
		HTTPAdapterPrefix: "",
		MsgContentType:    contentType,
		TLSVerification:   false,
	}
	var things []sdk.Thing

	mainfluxSDK := sdk.NewSDK(sdkConf)

	ch := sdk.Channel{Name: "test_channel"}
	cid, err := mainfluxSDK.CreateChannel(ch, token)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	tIDs := []string{}

	for i := 1; i < 101; i++ {
		th := sdk.Thing{Name: "test_device", Metadata: metadata}
		tid, err := mainfluxSDK.CreateThing(th, token)
		require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
		th.ID = tid
		tIDs = append(tIDs, tid)
		th.Key = fmt.Sprintf("%s%012d", keyPrefix, 2*i+1)
		things = append(things, th)
	}

	conIDs := sdk.ConnectionIDs{
		ChannelIDs: []string{cid},
		ThingIDs:   tIDs,
	}
	err = mainfluxSDK.Connect(conIDs, token)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	cases := []struct {
		desc     string
		channel  string
		token    string
		offset   uint64
		limit    uint64
		err      error
		response []sdk.Thing
	}{
		{
			desc:     "get a list of things by channel",
			channel:  cid,
			token:    token,
			offset:   0,
			limit:    5,
			err:      nil,
			response: things[0:5],
		},
		{
			desc:     "get a list of things by channel with invalid token",
			channel:  cid,
			token:    wrongValue,
			offset:   0,
			limit:    5,
			err:      createError(sdk.ErrFailedFetch, http.StatusForbidden),
			response: nil,
		},
		{
			desc:     "get a list of things by channel with empty token",
			channel:  cid,
			token:    "",
			offset:   0,
			limit:    5,
			err:      createError(sdk.ErrFailedFetch, http.StatusForbidden),
			response: nil,
		},
		{
			desc:     "get a list of things by channel with zero limit",
			channel:  cid,
			token:    token,
			offset:   0,
			limit:    0,
			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
			response: nil,
		},
		{
			desc:     "get a list of things by channel with limit greater than max",
			channel:  cid,
			token:    token,
			offset:   0,
			limit:    110,
			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
			response: nil,
		},
		{
			desc:     "get a list of things by channel with offset greater than max",
			channel:  cid,
			token:    token,
			offset:   110,
			limit:    5,
			err:      nil,
			response: []sdk.Thing{},
		},
		{
			desc:     "get a list of things by channel with invalid args (zero limit) and invalid token",
			channel:  cid,
			token:    wrongValue,
			offset:   0,
			limit:    0,
			err:      createError(sdk.ErrFailedFetch, http.StatusBadRequest),
			response: nil,
		},
	}
	for _, tc := range cases {
		page, err := mainfluxSDK.ThingsByChannel(tc.token, tc.channel, tc.offset, tc.limit)
		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
		assert.Equal(t, tc.response, page.Things, fmt.Sprintf("%s: expected response channel %s, got %s", tc.desc, tc.response, page.Things))
	}
}

func TestUpdateThing(t *testing.T) {
	svc := newThingsService(map[string]string{token: email})
	ts := newThingsServer(svc)
	defer ts.Close()
	sdkConf := sdk.Config{
		BaseURL:           ts.URL,
		UsersPrefix:       "",
		ThingsPrefix:      "",
		HTTPAdapterPrefix: "",
		MsgContentType:    contentType,
		TLSVerification:   false,
	}

	mainfluxSDK := sdk.NewSDK(sdkConf)
	id, err := mainfluxSDK.CreateThing(thing, token)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
	thing.Name = "test2"

	cases := []struct {
		desc  string
		thing sdk.Thing
		token string
		err   error
	}{
		{
			desc: "update existing thing",
			thing: sdk.Thing{
				ID:       id,
				Name:     "test_app",
				Metadata: metadata2,
			},
			token: token,
			err:   nil,
		},
		{
			desc: "update non-existing thing",
			thing: sdk.Thing{
				ID:       "0",
				Name:     "test_device",
				Metadata: metadata,
			},
			token: token,
			err:   createError(sdk.ErrFailedUpdate, http.StatusNotFound),
		},
		{
			desc: "update channel with invalid id",
			thing: sdk.Thing{
				ID:       "",
				Name:     "test_device",
				Metadata: metadata,
			},
			token: token,
			err:   createError(sdk.ErrFailedUpdate, http.StatusBadRequest),
		},
		{
			desc: "update channel with invalid token",
			thing: sdk.Thing{
				ID:       id,
				Name:     "test_app",
				Metadata: metadata2,
			},
			token: wrongValue,
			err:   createError(sdk.ErrFailedUpdate, http.StatusForbidden),
		},
		{
			desc: "update channel with empty token",
			thing: sdk.Thing{
				ID:       id,
				Name:     "test_app",
				Metadata: metadata2,
			},
			token: "",
			err:   createError(sdk.ErrFailedUpdate, http.StatusForbidden),
		},
	}

	for _, tc := range cases {
		err := mainfluxSDK.UpdateThing(tc.thing, tc.token)
		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
	}
}

func TestDeleteThing(t *testing.T) {
	svc := newThingsService(map[string]string{token: email})
	ts := newThingsServer(svc)
	defer ts.Close()
	sdkConf := sdk.Config{
		BaseURL:           ts.URL,
		UsersPrefix:       "",
		ThingsPrefix:      "",
		HTTPAdapterPrefix: "",
		MsgContentType:    contentType,
		TLSVerification:   false,
	}

	mainfluxSDK := sdk.NewSDK(sdkConf)
	id, err := mainfluxSDK.CreateThing(thing, token)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	cases := []struct {
		desc    string
		thingID string
		token   string
		err     error
	}{
		{
			desc:    "delete thing with invalid token",
			thingID: id,
			token:   wrongValue,
			err:     createError(sdk.ErrFailedRemoval, http.StatusForbidden),
		},
		{
			desc:    "delete non-existing thing",
			thingID: "2",
			token:   token,
			err:     nil,
		},
		{
			desc:    "delete thing with invalid id",
			thingID: "",
			token:   token,
			err:     createError(sdk.ErrFailedRemoval, http.StatusBadRequest),
		},
		{
			desc:    "delete thing with empty token",
			thingID: id,
			token:   "",
			err:     createError(sdk.ErrFailedRemoval, http.StatusForbidden),
		},
		{
			desc:    "delete existing thing",
			thingID: id,
			token:   token,
			err:     nil,
		},
		{
			desc:    "delete deleted thing",
			thingID: id,
			token:   token,
			err:     nil,
		},
	}

	for _, tc := range cases {
		err := mainfluxSDK.DeleteThing(tc.thingID, tc.token)
		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
	}
}

func TestConnectThing(t *testing.T) {
	svc := newThingsService(map[string]string{
		token:      email,
		otherToken: otherEmail,
	})

	ts := newThingsServer(svc)
	defer ts.Close()
	sdkConf := sdk.Config{
		BaseURL:           ts.URL,
		UsersPrefix:       "",
		ThingsPrefix:      "",
		HTTPAdapterPrefix: "",
		MsgContentType:    contentType,
		TLSVerification:   false,
	}

	mainfluxSDK := sdk.NewSDK(sdkConf)
	thingID, err := mainfluxSDK.CreateThing(thing, token)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	chanID1, err := mainfluxSDK.CreateChannel(channel, token)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	chanID2, err := mainfluxSDK.CreateChannel(channel, otherToken)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	cases := []struct {
		desc    string
		thingID string
		chanID  string
		token   string
		err     error
	}{
		{
			desc:    "connect existing thing to existing channel",
			thingID: thingID,
			chanID:  chanID1,
			token:   token,
			err:     nil,
		},

		{
			desc:    "connect existing thing to non-existing channel",
			thingID: thingID,
			chanID:  "9",
			token:   token,
			err:     createError(sdk.ErrFailedConnect, http.StatusNotFound),
		},
		{
			desc:    "connect non-existing thing to existing channel",
			thingID: "9",
			chanID:  chanID1,
			token:   token,
			err:     createError(sdk.ErrFailedConnect, http.StatusNotFound),
		},
		{
			desc:    "connect existing thing to channel with invalid ID",
			thingID: thingID,
			chanID:  "",
			token:   token,
			err:     createError(sdk.ErrFailedConnect, http.StatusBadRequest),
		},
		{
			desc:    "connect thing with invalid ID to existing channel",
			thingID: "",
			chanID:  chanID1,
			token:   token,
			err:     createError(sdk.ErrFailedConnect, http.StatusBadRequest),
		},

		{
			desc:    "connect existing thing to existing channel with invalid token",
			thingID: thingID,
			chanID:  chanID1,
			token:   wrongValue,
			err:     createError(sdk.ErrFailedConnect, http.StatusForbidden),
		},
		{
			desc:    "connect existing thing to existing channel with empty token",
			thingID: thingID,
			chanID:  chanID1,
			token:   "",
			err:     createError(sdk.ErrFailedConnect, http.StatusForbidden),
		},
		{
			desc:    "connect thing from owner to channel of other user",
			thingID: thingID,
			chanID:  chanID2,
			token:   token,
			err:     createError(sdk.ErrFailedConnect, http.StatusNotFound),
		},
	}

	for _, tc := range cases {
		conIDs := sdk.ConnectionIDs{
			ChannelIDs: []string{tc.chanID},
			ThingIDs:   []string{tc.thingID},
		}
		err := mainfluxSDK.Connect(conIDs, tc.token)
		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
	}
}

func TestConnect(t *testing.T) {
	svc := newThingsService(map[string]string{
		token:      email,
		otherToken: otherEmail,
	})

	ts := newThingsServer(svc)
	defer ts.Close()
	sdkConf := sdk.Config{
		BaseURL:           ts.URL,
		UsersPrefix:       "",
		ThingsPrefix:      "",
		HTTPAdapterPrefix: "",
		MsgContentType:    contentType,
		TLSVerification:   false,
	}

	mainfluxSDK := sdk.NewSDK(sdkConf)
	thingID, err := mainfluxSDK.CreateThing(thing, token)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	chanID1, err := mainfluxSDK.CreateChannel(channel, token)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	chanID2, err := mainfluxSDK.CreateChannel(channel, otherToken)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	cases := []struct {
		desc    string
		thingID string
		chanID  string
		token   string
		err     error
	}{
		{
			desc:    "connect existing things to existing channels",
			thingID: thingID,
			chanID:  chanID1,
			token:   token,
			err:     nil,
		},

		{
			desc:    "connect existing things to non-existing channels",
			thingID: thingID,
			chanID:  badID,
			token:   token,
			err:     createError(sdk.ErrFailedConnect, http.StatusNotFound),
		},
		{
			desc:    "connect non-existing things to existing channels",
			thingID: badID,
			chanID:  chanID1,
			token:   token,
			err:     createError(sdk.ErrFailedConnect, http.StatusNotFound),
		},
		{
			desc:    "connect existing things to channels with invalid ID",
			thingID: thingID,
			chanID:  emptyValue,
			token:   token,
			err:     createError(sdk.ErrFailedConnect, http.StatusBadRequest),
		},
		{
			desc:    "connect things with invalid ID to existing channels",
			thingID: emptyValue,
			chanID:  chanID1,
			token:   token,
			err:     createError(sdk.ErrFailedConnect, http.StatusBadRequest),
		},

		{
			desc:    "connect existing things to existing channels with invalid token",
			thingID: thingID,
			chanID:  chanID1,
			token:   wrongValue,
			err:     createError(sdk.ErrFailedConnect, http.StatusForbidden),
		},
		{
			desc:    "connect existing things to existing channels with empty token",
			thingID: thingID,
			chanID:  chanID1,
			token:   emptyValue,
			err:     createError(sdk.ErrFailedConnect, http.StatusForbidden),
		},
		{
			desc:    "connect things from owner to channels of other user",
			thingID: thingID,
			chanID:  chanID2,
			token:   token,
			err:     createError(sdk.ErrFailedConnect, http.StatusNotFound),
		},
	}

	for _, tc := range cases {
		connIDs := sdk.ConnectionIDs{
			[]string{tc.thingID},
			[]string{tc.chanID},
		}

		err := mainfluxSDK.Connect(connIDs, tc.token)
		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
	}
}

func TestDisconnectThing(t *testing.T) {
	svc := newThingsService(map[string]string{
		token:      email,
		otherToken: otherEmail,
	})

	ts := newThingsServer(svc)
	defer ts.Close()
	sdkConf := sdk.Config{
		BaseURL:           ts.URL,
		UsersPrefix:       "",
		ThingsPrefix:      "",
		HTTPAdapterPrefix: "",
		MsgContentType:    contentType,
		TLSVerification:   false,
	}

	mainfluxSDK := sdk.NewSDK(sdkConf)

	thingID, err := mainfluxSDK.CreateThing(thing, token)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	chanID1, err := mainfluxSDK.CreateChannel(channel, token)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	conIDs := sdk.ConnectionIDs{
		ChannelIDs: []string{chanID1},
		ThingIDs:   []string{thingID},
	}
	err = mainfluxSDK.Connect(conIDs, token)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	chanID2, err := mainfluxSDK.CreateChannel(channel, otherToken)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	cases := []struct {
		desc    string
		thingID string
		chanID  string
		token   string
		err     error
	}{
		{
			desc:    "disconnect connected thing from channel",
			thingID: thingID,
			chanID:  chanID1,
			token:   token,
			err:     nil,
		},
		{
			desc:    "disconnect existing thing from non-existing channel",
			thingID: thingID,
			chanID:  "9",
			token:   token,
			err:     createError(sdk.ErrFailedDisconnect, http.StatusNotFound),
		},
		{
			desc:    "disconnect non-existing thing from existing channel",
			thingID: "9",
			chanID:  chanID1,
			token:   token,
			err:     createError(sdk.ErrFailedDisconnect, http.StatusNotFound),
		},
		{
			desc:    "disconnect existing thing from channel with invalid ID",
			thingID: thingID,
			chanID:  "",
			token:   token,
			err:     createError(sdk.ErrFailedDisconnect, http.StatusBadRequest),
		},
		{
			desc:    "disconnect thing with invalid ID from existing channel",
			thingID: "",
			chanID:  chanID1,
			token:   token,
			err:     createError(sdk.ErrFailedDisconnect, http.StatusBadRequest),
		},
		{
			desc:    "disconnect existing thing from existing channel with invalid token",
			thingID: thingID,
			chanID:  chanID1,
			token:   wrongValue,
			err:     createError(sdk.ErrFailedDisconnect, http.StatusForbidden),
		},
		{
			desc:    "disconnect existing thing from existing channel with empty token",
			thingID: thingID,
			chanID:  chanID1,
			token:   "",
			err:     createError(sdk.ErrFailedDisconnect, http.StatusForbidden),
		},
		{
			desc:    "disconnect owner's thing from someone elses channel",
			thingID: thingID,
			chanID:  chanID2,
			token:   token,
			err:     createError(sdk.ErrFailedDisconnect, http.StatusNotFound),
		},
	}

	for _, tc := range cases {
		err := mainfluxSDK.DisconnectThing(tc.thingID, tc.chanID, tc.token)
		assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
	}
}
