package errs

import (
	"errors"
	"log"
	"net/http"
	"strconv"
	"testing"

	"github.com/smartystreets/goconvey/convey"
	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

func TestRegisterErrorType(t *testing.T) {
	convey.Convey("register error type", t, func() {
		convey.Convey("oneLevelInherit", func() {
			ErrMyCustomizedErr := RegisterErrorType(TiErr, http.StatusInternalServerError, 1)
			convey.So(IsError(ErrMyCustomizedErr, TiErr), convey.ShouldBeTrue)
			convey.So(ErrMyCustomizedErr.HTTPCode(), convey.ShouldEqual, 500)
			convey.So(ErrMyCustomizedErr.BizCode(), convey.ShouldEqual, 1)
			convey.So(len(ErrMyCustomizedErr.ErrMsg()), convey.ShouldBeGreaterThan, 0)
			convey.So(ErrMyCustomizedErr.Cause(), convey.ShouldEqual, nil)
		})

		convey.Convey("two level inherit", func() {
			ErrMyCustomizedErr := RegisterErrorType(TiErr, http.StatusInternalServerError, 1)
			ErrMyDeepCustomizedErr := RegisterErrorType(ErrMyCustomizedErr, http.StatusMethodNotAllowed, 2)

			convey.So(IsError(ErrMyDeepCustomizedErr, TiErr), convey.ShouldBeTrue)
			convey.So(ErrMyDeepCustomizedErr.HTTPCode(), convey.ShouldEqual, 405)
			convey.So(ErrMyDeepCustomizedErr.BizCode(), convey.ShouldEqual, 2)
			convey.So(len(ErrMyDeepCustomizedErr.ErrMsg()), convey.ShouldBeGreaterThan, 0)
			convey.So(ErrMyDeepCustomizedErr.Cause(), convey.ShouldEqual, nil)
		})
	})

	convey.Convey("test new error", t, func() {
		convey.Convey("should not init TiErr", func() {
			convey.ShouldPanic(func() {
				NewError(TiErr, "oh no", nil) //nolint:errcheck
			})
		})

		convey.Convey("new error", func() {
			cause := errors.New("oh, a unknown cause")

			err := NewError(ErrInternalServerError, "whoops", cause)
			convey.So(IsError(err, ErrInternalServerError), convey.ShouldBeTrue)
			convey.So(IsError(err, TiErr), convey.ShouldBeTrue)
			convey.So(err.HTTPCode(), convey.ShouldEqual, 500)
			convey.So(err.BizCode(), convey.ShouldEqual, ErrCodeInternalServerError)
			convey.So(err.ErrMsg(), convey.ShouldEqual, "whoops")
			convey.So(err.Cause(), convey.ShouldEqual, cause)

			convey.So(IsError(err, cause), convey.ShouldBeFalse)
		})

		convey.Convey("new inherit error", func() {
			ErrMyCustomizedErr := RegisterErrorType(TiErr, http.StatusInternalServerError, 1)
			ErrMyDeepCustomizedErr := RegisterErrorType(ErrMyCustomizedErr, http.StatusMethodNotAllowed, 2)

			convey.So(IsError(ErrMyDeepCustomizedErr, TiErr), convey.ShouldBeTrue)
			convey.So(ErrMyDeepCustomizedErr.HTTPCode(), convey.ShouldEqual, 405)
			convey.So(ErrMyDeepCustomizedErr.BizCode(), convey.ShouldEqual, 2)
			convey.So(len(ErrMyDeepCustomizedErr.ErrMsg()), convey.ShouldBeGreaterThan, 0)
			convey.So(ErrMyDeepCustomizedErr.Cause(), convey.ShouldEqual, nil)

			cause := errors.New("oh, a unknown cause")

			err := NewError(ErrMyDeepCustomizedErr, "whoops", cause)
			convey.So(IsError(err, ErrMyDeepCustomizedErr), convey.ShouldBeTrue)
			convey.So(IsError(err, ErrMyCustomizedErr), convey.ShouldBeTrue)
			convey.So(IsError(err, TiErr), convey.ShouldBeTrue)
			convey.So(err.HTTPCode(), convey.ShouldEqual, 405)
			convey.So(err.BizCode(), convey.ShouldEqual, 2)
			convey.So(err.ErrMsg(), convey.ShouldEqual, "whoops")
			convey.So(err.Cause(), convey.ShouldEqual, cause)

			convey.So(IsError(err, cause), convey.ShouldBeFalse)
		})

		convey.Convey("test wrap error", func() {
			cause := errors.New("oh, a unknown cause")

			err := NewError(ErrInternalServerError, "whoops", cause)
			wrapped := WrapError(err, "wrapped")
			convey.So(IsError(err, ErrInternalServerError), convey.ShouldBeTrue)
			convey.So(IsError(err, TiErr), convey.ShouldBeTrue)
			convey.So(IsError(wrapped, err), convey.ShouldBeTrue)
			convey.So(IsError(wrapped, ErrInternalServerError), convey.ShouldBeTrue)
			convey.So(IsError(wrapped, TiErr), convey.ShouldBeTrue)
		})
	})
}

func TestUtilityFunctions(t *testing.T) {
	type fn func(string, error) Error
	var fns = map[Error]fn{
		ErrBadRequest:                     NewBadRequest,
		ErrUnauthorized:                   NewUnauthorized,
		ErrPaymentRequired:                NewPaymentRequired,
		ErrForbidden:                      NewForbidden,
		ErrNotFound:                       NewNotFound,
		ErrMethodNotAllowed:               NewMethodNotAllowed,
		ErrNotAcceptable:                  NewNotAcceptable,
		ErrRequestTimeout:                 NewRequestTimeout,
		ErrConflict:                       NewConflict,
		ErrLengthRequired:                 NewLengthRequired,
		ErrPreconditionFailed:             NewPreconditionFailed,
		ErrRequestEntityTooLarge:          NewRequestEntityTooLarge,
		ErrRequestURITooLong:              NewRequestURITooLong,
		ErrUnsupportedMediaType:           NewUnsupportedMediaType,
		ErrExpectationFailed:              NewExpectationFailed,
		ErrTooManyRequests:                NewTooManyRequests,
		ErrInternalServerError:            NewInternalServerError,
		ErrNotImplemented:                 NewNotImplemented,
		ErrBadGateway:                     NewBadGateway,
		ErrServiceUnavailable:             NewServiceUnavailable,
		ErrRecordExist:                    NewRecordExist,
		ErrIPBlocked:                      NewIPBlocked,
		ErrUnderMaintenance:               NewUnderMaintenance,
		ErrInternalDB:                     NewInternalDB,
		ErrInternalRedis:                  NewInternalRedis,
		ErrInternalMQ:                     NewInternalMQ,
		ErrInternalPanic:                  NewInternalPanic,
		ErrInternalJsonMarshal:            NewInternalJsonMarshal,
		ErrInternalTimeout:                NewInternalTimeout,
		ErrInternalUnknown:                NewInternalUnknown,
		ErrInternalHTTP:                   NewInternalHTTP,
		ErrInternalGRPC:                   NewInternalGRPC,
		ErrInternalSvcBilling:             NewInternalSvcBilling,
		ErrInternalSvcInfraAPI:            NewInternalSvcInfraAPI,
		ErrInternalSvcInfraMetering:       NewInternalSvcInfraMetering,
		ErrInternalGeneralBusiness:        NewInternalGeneralBusiness,
		ErrInternalOptimisticLockConflict: NewInternalOptimisticLockConflict,
	}

	convey.Convey("test utilities", t, func() {
		cause := errors.New("oh no")
		for v, f := range fns {
			err := f("test", cause)
			convey.So(IsError(err, v), convey.ShouldBeTrue)
			convey.So(IsError(err, cause), convey.ShouldBeFalse)
		}
	})
}

func TestWrapGORMError(t *testing.T) {
	convey.Convey("test wrap gorm error", t, func() {
		convey.Convey("nil", func() {
			wrapped := WrapGORMError(nil, "reason")

			convey.So(IsError(wrapped, nil), convey.ShouldBeTrue)
			convey.So(IsError(wrapped, TiErr), convey.ShouldBeFalse)
		})

		convey.Convey("error", func() {
			err := errors.New("I don't know why")
			wrapped := WrapGORMError(err, "reason")

			convey.So(IsError(wrapped, ErrInternalDB), convey.ShouldBeTrue)
		})

		convey.Convey("tierr", func() {
			err := NewBadRequest("I don't know why", nil)
			wrapped := WrapGORMError(err, "reason")

			convey.So(IsError(wrapped, ErrBadRequest), convey.ShouldBeTrue)
			convey.So(IsError(wrapped, TiErr), convey.ShouldBeTrue)
		})

		convey.Convey("gorm.ErrRecordNotFound", func() {
			wrapped := WrapGORMError(gorm.ErrRecordNotFound, "reason")

			convey.So(IsError(wrapped, ErrNotFound), convey.ShouldBeTrue)
			convey.So(IsError(wrapped, TiErr), convey.ShouldBeTrue)
		})
	})
}

func TestAsError(t *testing.T) {
	convey.Convey("test as error", t, func() {
		convey.Convey("as", func() {
			err := NewInternalServerError("hello", nil)

			wrapped := WrapError(err, "world")

			asErr, ok := AsTiError(wrapped)
			convey.So(ok, convey.ShouldBeTrue)
			convey.So(asErr.BizCode(), convey.ShouldEqual, err.BizCode())
			convey.So(asErr.HTTPCode(), convey.ShouldEqual, err.HTTPCode())
			convey.So(asErr.ErrMsg(), convey.ShouldEqual, err.ErrMsg())
		})

		convey.Convey("can't as", func() {
			err := errors.New("bad")

			wrapped := WrapError(err, "world")

			asErr, ok := AsTiError(wrapped)
			convey.So(ok, convey.ShouldBeFalse)
			convey.So(asErr, convey.ShouldBeNil)
		})
	})
}
func TestErrorAndBaseRespConvertor(t *testing.T) {
	convey.Convey("test error and base_resp convertor", t, func() {
		err := NewInternalServerError("hello", nil)
		var resp base.BaseResp
		SetTiErrToBaseResp(err, &resp)
		new_err := GetTiErrFromBaseResp(&resp)
		logger, _ := zap.NewProduction()
		defer logger.Sync() //nolint:errcheck
		_, isTiErr := AsTiError(new_err)
		logger.Error("new_tierror is tierror " + strconv.FormatBool(isTiErr))
		logger.Error("test tierr format", zap.Error(err))
		logger.Error("test tierr new_format", zap.Error(new_err))
	})
}
func TestPrintError(t *testing.T) {
	e := NewInternalGRPC("internal grpc", nil)
	e2 := NewInternalDB("internal db", e)

	log.Printf("%s, %q, %v, %+v", e, e, e, e)
	log.Printf("%s, %q, %v, %+v", e2, e2, e2, e2)
	logger, _ := zap.NewProduction()
	defer logger.Sync() //nolint:errcheck

	logger.Error("test tierr format", zap.Error(e))
	logger.Error("test tierr format", zap.Error(e2))
}
