package exerr

import (
	"encoding/json"
	"errors"
	"fmt"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/bsoncodec"
	"go.mongodb.org/mongo-driver/bson/bsonrw"
	"go.mongodb.org/mongo-driver/bson/bsontype"
	"reflect"
)

type ErrorSeverity struct{ Severity string }

var (
	SevTrace = ErrorSeverity{"Trace"}
	SevDebug = ErrorSeverity{"Debug"}
	SevInfo  = ErrorSeverity{"Info"}
	SevWarn  = ErrorSeverity{"Warn"}
	SevErr   = ErrorSeverity{"Err"}
	SevFatal = ErrorSeverity{"Fatal"}
)

func (e *ErrorSeverity) UnmarshalJSON(bytes []byte) error {
	return json.Unmarshal(bytes, &e.Severity)
}

func (e ErrorSeverity) MarshalJSON() ([]byte, error) {
	return json.Marshal(e.Severity)
}

func (e *ErrorSeverity) UnmarshalBSONValue(bt bsontype.Type, data []byte) error {
	if bt == bson.TypeNull {
		// we can't set nil in UnmarshalBSONValue (so we use default(struct))
		// Use mongoext.CreateGoExtBsonRegistry if you need to unmarsh pointer values
		// https://stackoverflow.com/questions/75167597
		// https://jira.mongodb.org/browse/GODRIVER-2252
		*e = ErrorSeverity{}
		return nil
	}
	if bt != bson.TypeString {
		return errors.New(fmt.Sprintf("cannot unmarshal %v into String", bt))
	}
	var tt string
	err := bson.RawValue{Type: bt, Value: data}.Unmarshal(&tt)
	if err != nil {
		return err
	}
	*e = ErrorSeverity{tt}
	return nil
}

func (e ErrorSeverity) MarshalBSONValue() (bsontype.Type, []byte, error) {
	return bson.MarshalValue(e.Severity)
}

func (e ErrorSeverity) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
	if val.Kind() == reflect.Ptr && val.IsNil() {
		if !val.CanSet() {
			return errors.New("ValueUnmarshalerDecodeValue")
		}
		val.Set(reflect.New(val.Type().Elem()))
	}

	tp, src, err := bsonrw.Copier{}.CopyValueToBytes(vr)
	if err != nil {
		return err
	}

	if val.Kind() == reflect.Ptr && len(src) == 0 {
		val.Set(reflect.Zero(val.Type()))
		return nil
	}

	err = e.UnmarshalBSONValue(tp, src)
	if err != nil {
		return err
	}

	if val.Kind() == reflect.Ptr {
		val.Set(reflect.ValueOf(&e))
	} else {
		val.Set(reflect.ValueOf(e))
	}

	return nil
}

//goland:noinspection GoUnusedGlobalVariable
var AllSeverities = []ErrorSeverity{SevTrace, SevDebug, SevInfo, SevWarn, SevErr, SevFatal}