package exerr

import (
	"encoding/json"
	"fmt"
	"github.com/rs/zerolog/log"
	"gogs.mikescher.com/BlackForestBytes/goext/enums"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"strings"
)

//
// These are wrapper objects, because for some metadata-types we need to serialize a bit more complex data
// (eg thy actual type for ID objects, or the json representation for any types)
//

type IDWrap struct {
	Type  string
	Value string
	IsNil bool
}

func newIDWrap(val fmt.Stringer) IDWrap {
	t := fmt.Sprintf("%T", val)
	arr := strings.Split(t, ".")
	if len(arr) > 0 {
		t = arr[len(arr)-1]
	}

	if langext.IsNil(val) {
		return IDWrap{Type: t, Value: "", IsNil: true}
	}

	v := val.String()
	return IDWrap{Type: t, Value: v, IsNil: false}
}

func (w IDWrap) Serialize() string {
	if w.IsNil {
		return "!nil" + ":" + w.Type
	}
	return w.Type + ":" + w.Value
}

func (w IDWrap) String() string {
	if w.IsNil {
		return w.Type + "<<nil>>"
	}
	return w.Type + "(" + w.Value + ")"
}

func deserializeIDWrap(v string) IDWrap {
	r := strings.SplitN(v, ":", 2)

	if len(r) == 2 && r[0] == "!nil" {
		return IDWrap{Type: r[1], Value: v, IsNil: true}
	}

	if len(r) == 0 {
		return IDWrap{}
	} else if len(r) == 1 {
		return IDWrap{Type: "", Value: v, IsNil: false}
	} else {
		return IDWrap{Type: r[0], Value: r[1], IsNil: false}
	}
}

type AnyWrap struct {
	Type    string
	Json    string
	IsError bool
	IsNil   bool
}

func newAnyWrap(val any) (result AnyWrap) {
	result = AnyWrap{Type: "", Json: "", IsError: true, IsNil: false} // ensure a return in case of recover()

	defer func() {
		if err := recover(); err != nil {
			// send error should never crash our program
			log.Error().Interface("err", err).Msg("Panic while trying to marshal anywrap ( bmerror.Interface )")
		}
	}()

	t := fmt.Sprintf("%T", val)

	if langext.IsNil(val) {
		return AnyWrap{Type: t, Json: "", IsError: false, IsNil: true}
	}

	j, err := json.Marshal(val)
	if err == nil {
		return AnyWrap{Type: t, Json: string(j), IsError: false, IsNil: false}
	} else {
		return AnyWrap{Type: t, Json: "", IsError: true, IsNil: false}
	}
}

func (w AnyWrap) Serialize() string {
	if w.IsError {
		return "ERR" + ":" + w.Type + ":" + w.Json
	} else if w.IsNil {
		return "NIL" + ":" + w.Type + ":" + w.Json
	} else {
		return "OK" + ":" + w.Type + ":" + w.Json
	}
}

func (w AnyWrap) String() string {
	if w.IsError {
		return "(error)"
	} else if w.IsNil {
		return "(nil)"
	} else {
		return w.Json
	}
}

func deserializeAnyWrap(v string) AnyWrap {
	r := strings.SplitN(v, ":", 3)
	if len(r) != 3 {
		return AnyWrap{IsError: true, Type: "", Json: "", IsNil: false}
	} else {
		if r[0] == "OK" {
			return AnyWrap{IsError: false, Type: r[1], Json: r[2], IsNil: false}
		} else if r[0] == "ERR" {
			return AnyWrap{IsError: true, Type: r[1], Json: r[2], IsNil: false}
		} else if r[0] == "NIL" {
			return AnyWrap{IsError: false, Type: r[1], Json: "", IsNil: true}
		} else {
			return AnyWrap{IsError: true, Type: "", Json: "", IsNil: false}
		}
	}
}

type EnumWrap struct {
	Type        string
	ValueString string
	ValueRaw    enums.Enum // `ValueRaw` is lost during serialization roundtrip
	IsNil       bool
}

func newEnumWrap(val enums.Enum) EnumWrap {
	t := fmt.Sprintf("%T", val)
	arr := strings.Split(t, ".")
	if len(arr) > 0 {
		t = arr[len(arr)-1]
	}

	if langext.IsNil(val) {
		return EnumWrap{Type: t, ValueString: "", ValueRaw: val, IsNil: true}
	}

	if enumstr, ok := val.(enums.StringEnum); ok {
		return EnumWrap{Type: t, ValueString: enumstr.String(), ValueRaw: val, IsNil: false}
	}

	return EnumWrap{Type: t, ValueString: fmt.Sprintf("%v", val), ValueRaw: val, IsNil: false}
}

func (w EnumWrap) Serialize() string {
	if w.IsNil {
		return "!nil" + ":" + w.Type
	}
	return w.Type + ":" + w.ValueString
}

func (w EnumWrap) String() string {
	if w.IsNil {
		return w.Type + "<<nil>>"
	}
	return "[" + w.Type + "] " + w.ValueString
}

func deserializeEnumWrap(v string) EnumWrap {
	r := strings.SplitN(v, ":", 2)

	if len(r) == 2 && r[0] == "!nil" {
		return EnumWrap{Type: r[1], ValueString: v, ValueRaw: nil, IsNil: true}
	}

	if len(r) == 0 {
		return EnumWrap{}
	} else if len(r) == 1 {
		return EnumWrap{Type: "", ValueString: v, ValueRaw: nil, IsNil: false}
	} else {
		return EnumWrap{Type: r[0], ValueString: r[1], ValueRaw: nil, IsNil: false}
	}
}