package mongo

import (
	"errors"
	"testing"

	"github.com/globalsign/mgo"
	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
	"github.com/tal-tech/go-zero/core/breaker"
	"github.com/tal-tech/go-zero/core/logx"
	"github.com/tal-tech/go-zero/core/stores/mongo/internal"
	"github.com/tal-tech/go-zero/core/stringx"
)

var errDummy = errors.New("dummy")

func init() {
	logx.Disable()
}

func TestKeepPromise_accept(t *testing.T) {
	p := new(mockPromise)
	kp := keepablePromise{
		promise: p,
		log:     func(error) {},
	}
	assert.Nil(t, kp.accept(nil))
	assert.Equal(t, mgo.ErrNotFound, kp.accept(mgo.ErrNotFound))
}

func TestKeepPromise_keep(t *testing.T) {
	tests := []struct {
		err      error
		accepted bool
		reason   string
	}{
		{
			err:      nil,
			accepted: true,
			reason:   "",
		},
		{
			err:      mgo.ErrNotFound,
			accepted: true,
			reason:   "",
		},
		{
			err:      errors.New("any"),
			accepted: false,
			reason:   "any",
		},
	}

	for _, test := range tests {
		t.Run(stringx.RandId(), func(t *testing.T) {
			p := new(mockPromise)
			kp := keepablePromise{
				promise: p,
				log:     func(error) {},
			}
			assert.Equal(t, test.err, kp.keep(test.err))
			assert.Equal(t, test.accepted, p.accepted)
			assert.Equal(t, test.reason, p.reason)
		})
	}
}

func TestNewCollection(t *testing.T) {
	col := newCollection(&mgo.Collection{
		Database: nil,
		Name:     "foo",
		FullName: "bar",
	})
	assert.Equal(t, "bar", col.(*decoratedCollection).name)
}

func TestCollectionFind(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	var query mgo.Query
	col := internal.NewMockMgoCollection(ctrl)
	col.EXPECT().Find(gomock.Any()).Return(&query)
	c := decoratedCollection{
		collection: col,
		brk:        breaker.NewBreaker(),
	}
	actual := c.Find(nil)
	switch v := actual.(type) {
	case promisedQuery:
		assert.Equal(t, &query, v.Query)
		assert.Equal(t, errDummy, v.promise.keep(errDummy))
	default:
		t.Fail()
	}
	c.brk = new(dropBreaker)
	actual = c.Find(nil)
	assert.Equal(t, rejectedQuery{}, actual)
}

func TestCollectionFindId(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	var query mgo.Query
	col := internal.NewMockMgoCollection(ctrl)
	col.EXPECT().FindId(gomock.Any()).Return(&query)
	c := decoratedCollection{
		collection: col,
		brk:        breaker.NewBreaker(),
	}
	actual := c.FindId(nil)
	switch v := actual.(type) {
	case promisedQuery:
		assert.Equal(t, &query, v.Query)
		assert.Equal(t, errDummy, v.promise.keep(errDummy))
	default:
		t.Fail()
	}
	c.brk = new(dropBreaker)
	actual = c.FindId(nil)
	assert.Equal(t, rejectedQuery{}, actual)
}

func TestCollectionInsert(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	col := internal.NewMockMgoCollection(ctrl)
	col.EXPECT().Insert(nil, nil).Return(errDummy)
	c := decoratedCollection{
		collection: col,
		brk:        breaker.NewBreaker(),
	}
	err := c.Insert(nil, nil)
	assert.Equal(t, errDummy, err)
	c.brk = new(dropBreaker)
	err = c.Insert(nil, nil)
	assert.Equal(t, errDummy, err)
}

func TestCollectionPipe(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	var pipe mgo.Pipe
	col := internal.NewMockMgoCollection(ctrl)
	col.EXPECT().Pipe(gomock.Any()).Return(&pipe)
	c := decoratedCollection{
		collection: col,
		brk:        breaker.NewBreaker(),
	}
	actual := c.Pipe(nil)
	switch v := actual.(type) {
	case promisedPipe:
		assert.Equal(t, &pipe, v.Pipe)
		assert.Equal(t, errDummy, v.promise.keep(errDummy))
	default:
		t.Fail()
	}
	c.brk = new(dropBreaker)
	actual = c.Pipe(nil)
	assert.Equal(t, rejectedPipe{}, actual)
}

func TestCollectionRemove(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	col := internal.NewMockMgoCollection(ctrl)
	col.EXPECT().Remove(gomock.Any()).Return(errDummy)
	c := decoratedCollection{
		collection: col,
		brk:        breaker.NewBreaker(),
	}
	err := c.Remove(nil)
	assert.Equal(t, errDummy, err)
	c.brk = new(dropBreaker)
	err = c.Remove(nil)
	assert.Equal(t, errDummy, err)
}

func TestCollectionRemoveAll(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	col := internal.NewMockMgoCollection(ctrl)
	col.EXPECT().RemoveAll(gomock.Any()).Return(nil, errDummy)
	c := decoratedCollection{
		collection: col,
		brk:        breaker.NewBreaker(),
	}
	_, err := c.RemoveAll(nil)
	assert.Equal(t, errDummy, err)
	c.brk = new(dropBreaker)
	_, err = c.RemoveAll(nil)
	assert.Equal(t, errDummy, err)
}

func TestCollectionRemoveId(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	col := internal.NewMockMgoCollection(ctrl)
	col.EXPECT().RemoveId(gomock.Any()).Return(errDummy)
	c := decoratedCollection{
		collection: col,
		brk:        breaker.NewBreaker(),
	}
	err := c.RemoveId(nil)
	assert.Equal(t, errDummy, err)
	c.brk = new(dropBreaker)
	err = c.RemoveId(nil)
	assert.Equal(t, errDummy, err)
}

func TestCollectionUpdate(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	col := internal.NewMockMgoCollection(ctrl)
	col.EXPECT().Update(gomock.Any(), gomock.Any()).Return(errDummy)
	c := decoratedCollection{
		collection: col,
		brk:        breaker.NewBreaker(),
	}
	err := c.Update(nil, nil)
	assert.Equal(t, errDummy, err)
	c.brk = new(dropBreaker)
	err = c.Update(nil, nil)
	assert.Equal(t, errDummy, err)
}

func TestCollectionUpdateId(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	col := internal.NewMockMgoCollection(ctrl)
	col.EXPECT().UpdateId(gomock.Any(), gomock.Any()).Return(errDummy)
	c := decoratedCollection{
		collection: col,
		brk:        breaker.NewBreaker(),
	}
	err := c.UpdateId(nil, nil)
	assert.Equal(t, errDummy, err)
	c.brk = new(dropBreaker)
	err = c.UpdateId(nil, nil)
	assert.Equal(t, errDummy, err)
}

func TestCollectionUpsert(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	col := internal.NewMockMgoCollection(ctrl)
	col.EXPECT().Upsert(gomock.Any(), gomock.Any()).Return(nil, errDummy)
	c := decoratedCollection{
		collection: col,
		brk:        breaker.NewBreaker(),
	}
	_, err := c.Upsert(nil, nil)
	assert.Equal(t, errDummy, err)
	c.brk = new(dropBreaker)
	_, err = c.Upsert(nil, nil)
	assert.Equal(t, errDummy, err)
}

type mockPromise struct {
	accepted bool
	reason   string
}

func (p *mockPromise) Accept() {
	p.accepted = true
}

func (p *mockPromise) Reject(reason string) {
	p.reason = reason
}

type dropBreaker struct {
}

func (d *dropBreaker) Name() string {
	return "dummy"
}

func (d *dropBreaker) Allow() (breaker.Promise, error) {
	return nil, errDummy
}

func (d *dropBreaker) Do(req func() error) error {
	return nil
}

func (d *dropBreaker) DoWithAcceptable(req func() error, acceptable breaker.Acceptable) error {
	return errDummy
}

func (d *dropBreaker) DoWithFallback(req func() error, fallback func(err error) error) error {
	return nil
}

func (d *dropBreaker) DoWithFallbackAcceptable(req func() error, fallback func(err error) error,
	acceptable breaker.Acceptable) error {
	return nil
}
