package bizerr

import (
	"fmt"
	"testing"

	"github.com/go-sql-driver/mysql"
	. "github.com/smartystreets/goconvey/convey"
	"gorm.io/gorm"
)

func TestParseGormErr2code(t *testing.T) {
	Convey("TestParseGormErr2code", t, func() {
		Convey("should return ErrCodeOK if err is nil", func() {
			code := ParseGormErr2code(nil)
			So(code, ShouldEqual, ErrCodeOK)
		})
		Convey("should return directly if err is BizError", func() {
			code := ParseGormErr2code(&BizErr{Code: ErrCodeParams})
			So(code, ShouldEqual, ErrCodeParams)
		})
		Convey("should return ErrCodeRecordNotFound if gorm.ErrRecordNotFound is given", func() {
			code := ParseGormErr2code(gorm.ErrRecordNotFound)
			So(code, ShouldEqual, ErrCodeRecordNotFound)
		})
		Convey("should return ErrCodeRecordExist if the error is mysql.MysqlError and code is 1062", func() {
			err := &mysql.MySQLError{
				Number:  mysqlErrCodeDuplicatedKey,
				Message: "test",
			}
			code := ParseGormErr2code(err)
			So(code, ShouldEqual, ErrCodeRecordExist)
		})
		Convey("should return ErrCodeDB if gorm error but non gorm.ErrRecordNotFound is given", func() {
			code := ParseGormErr2code(gorm.ErrInvalidData)
			So(code, ShouldEqual, ErrCodeDB)
		})
	})
}

func TestWrapGormError(t *testing.T) {
	Convey("TestWrapGormError", t, func() {
		Convey("should return nil if err is nil", func() {
			err := WrapGormError(nil, "a:%d", 1)
			So(err, ShouldBeNil)
		})
		Convey("should return directly if err is BizError", func() {
			be := &BizErr{Code: ErrCodeParams}
			err := WrapGormError(be, "")
			So(err, ShouldEqual, be)
		})
		Convey("should return RecordNotFound error if gorm.ErrRecordNotFound is given", func() {
			err := WrapGormError(gorm.ErrRecordNotFound, "a:%d", 1)
			So(err.ErrCode(), ShouldEqual, ErrCodeRecordNotFound)
		})
	})
}

func TestWrapGrpcError(t *testing.T) {
	Convey("TestWrapGrpcError", t, func() {
		Convey("normal case", func() {
			e := fmt.Errorf("timeout")
			err := WrapGrpcError(e, "abc: %d", 1)
			So(err.RawErrCode(), ShouldEqual, ErrCodeGRPC)
			So(err.IsException(), ShouldBeTrue)
			So(err.Error(), ShouldEndWith, "code=159900106,msg=abc: 1: timeout")
			So(Message(err), ShouldEqual, "abc: 1: timeout")
		})
	})
}

func TestWrapError(t *testing.T) {
	Convey("TestWrapError", t, func() {
		Convey("normal case", func() {
			e := fmt.Errorf("timeout")
			err := WrapError(e, "abc: %d", 1)
			So(err.RawErrCode(), ShouldEqual, ErrCodeUnknown)
			So(err.IsException(), ShouldBeTrue)
			So(err.Error(), ShouldEndWith, "code=159900104,msg=abc: 1: timeout")
			So(Message(err), ShouldEqual, "abc: 1: timeout")
		})
	})
}
func TestWrapErrorWithCode(t *testing.T) {
	Convey("TestWrapErrorWithCode", t, func() {
		Convey("normal case", func() {
			e := fmt.Errorf("timeout")

			err := WrapErrorWithCode(e, ErrCodeParams, "abc: %d", 1)
			So(err.RawErrCode(), ShouldEqual, ErrCodeParams)
			So(err.IsException(), ShouldBeFalse)
			So(err.Error(), ShouldEndWith, "code=49900001,msg=abc: 1: timeout")
			So(Message(err), ShouldEqual, "abc: 1: timeout")
		})
		Convey("should return nil if err is nil", func() {
			err := WrapErrorWithCode(nil, ErrCodeParams, "abc: %d", 1)
			So(err, ShouldBeNil)
		})
		Convey("should return directly if err is bizError", func() {
			be := New(ErrCodeDB, "db")
			err := WrapErrorWithCode(be, ErrCodeParams, "abc: %d", 1)
			So(err, ShouldEqual, be)
		})
	})
}

func TestPipeline(t *testing.T) {
	Convey("TestPipeline", t, func() {
		Convey("should return nil if all fn succ", func() {
			err := Pipeline(
				func() error {
					return nil
				}, func() error {
					return nil
				}, func() error {
					return nil
				})
			So(err, ShouldBeNil)
		})
		Convey("should return err if any fn return error", func() {
			err := Pipeline(
				func() error {
					return nil
				}, func() error {
					return gorm.ErrRecordNotFound
				}, func() error {
					return nil
				})
			So(err, ShouldNotBeNil)
		})
	})
}

func TestMessage(t *testing.T) {
	Convey("TestMessage", t, func() {
		e := fmt.Errorf("timeout")
		So(Message(e), ShouldEqual, "timeout")
		e2 := WrapGrpcError(e, "grpc failed")
		So(Message(e2), ShouldEqual, "grpc failed: timeout")
		e3 := WrapError(e2, "call client failed")
		So(Message(e3), ShouldEqual, "grpc failed: timeout")
	})
}
