// Package zapx 提供了 zap 日志的对接
package zapx

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strconv"
	"time"

	"go.uber.org/zap/buffer"
	"go.uber.org/zap/zapcore"
)

var pool = buffer.NewPool()

// 创建新的 encoder 对象
func NewEncoder(category string, formatter CallerFormatFunc) zapcore.Encoder {
	return &encoder{
		key:              "",
		category:         category,
		formatCallerPath: formatter,
		entries: &recordEntries{
			stringEntries: []StringEntry{},
			floatEntries:  []FloatEntry{},
			intEntries:    []IntEntry{},
			boolEntries:   []BoolEntry{},
		},
	}
}

// 调用方路径格式化函数类型
type CallerFormatFunc = func(zapcore.EntryCaller) string

// 格式化为全路径
func CallerFullPath(c zapcore.EntryCaller) string { return c.FullPath() }

// 格式化为相对路径
func CallerTrimmedPath(c zapcore.EntryCaller) string { return c.TrimmedPath() }

// 日志记录的参数
type recordEntries struct {
	stringEntries []StringEntry
	floatEntries  []FloatEntry
	intEntries    []IntEntry
	boolEntries   []BoolEntry
}

// 因为 json number 都是 double 型数字，最高有效位只有 53 位。所以为了避免精度丢失，需要对 int64 类型数据做以下处理：
//   - 能被 int32 表示的数据当作 int 数字处理。
//   - 在 double 类型安全范围内的数字当作 float64 数字处理。
//   - 无法用 double 表示的数字当作 string 处理。
func addInt64(r *recordEntries, key string, value int64) {
	switch {
	case int64(int32(value)) == value:
		r.intEntries = append(r.intEntries, IntEntry{Key: key, Value: int32(value)})
	case int64(float64(value)) == value:
		r.floatEntries = append(r.floatEntries, FloatEntry{Key: key, Value: float64(value)})
	default:
		r.stringEntries = append(r.stringEntries, StringEntry{Key: key, Value: strconv.FormatInt(value, 10)})
	}
}

// uint 情况同上，转换规则为：
//   - 能被 int32 表示的数据当作 int 数字处理。
//   - 在 double 类型安全范围内的数字当作 float64 数字处理。
//   - 无法用 double 表示的数字当作 string 处理。
func addUint64(r *recordEntries, key string, value uint64) {
	switch {
	case uint64(int32(value)) == value:
		r.intEntries = append(r.intEntries, IntEntry{Key: key, Value: int32(value)})
	case uint64(float64(value)) == value:
		r.floatEntries = append(r.floatEntries, FloatEntry{Key: key, Value: float64(value)})
	default:
		r.stringEntries = append(r.stringEntries, StringEntry{Key: key, Value: strconv.FormatUint(value, 10)})
	}
}

type encoder struct {
	key              string
	category         string
	formatCallerPath func(zapcore.EntryCaller) string
	entries          *recordEntries
}

func (e *encoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
	var caller string
	if entry.Caller != (zapcore.EntryCaller{}) {
		caller = e.formatCallerPath(entry.Caller)
	}

	r := LogRecord{
		EventTime:     entry.Time,
		Category:      e.category,
		Level:         entry.Level.String(),
		Caller:        caller,
		Message:       entry.Message,
		StringEntries: e.entries.stringEntries,
		FloatEntries:  e.entries.floatEntries,
		IntEntries:    e.entries.intEntries,
		BoolEntries:   e.entries.boolEntries,
	}
	enc := &objectEncoder{
		key: e.key,
		entries: &recordEntries{
			stringEntries: []StringEntry{},
			floatEntries:  []FloatEntry{},
			intEntries:    []IntEntry{},
			boolEntries:   []BoolEntry{},
		},
	}
	if entry.LoggerName != "" {
		enc.AddString("loggerName", entry.LoggerName)
	}
	for _, field := range fields {
		field.AddTo(enc)
	}
	if entry.Stack != "" {
		enc.AddString("stack", entry.Stack)
	}
	r.StringEntries = append(r.StringEntries, enc.entries.stringEntries...)
	r.FloatEntries = append(r.FloatEntries, enc.entries.floatEntries...)
	r.IntEntries = append(r.IntEntries, enc.entries.intEntries...)
	r.BoolEntries = append(r.BoolEntries, enc.entries.boolEntries...)

	bs, err := json.Marshal(r)
	if err != nil {
		return nil, err
	}
	buf := pool.Get()
	_, err = buf.Write(bs)
	if err != nil {
		return nil, err
	}
	return buf, nil
}
func (e *encoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error {
	return marshaler.MarshalLogArray(&sliceEncoder{key: addKey(e.key, key)})
}

func (e *encoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error {
	return marshaler.MarshalLogObject(&objectEncoder{key: addKey(e.key, key)})
}

// 不支持纯二进制类型，用 Base64 编码一下。
func (e *encoder) AddBinary(key string, value []byte) {
	v := base64.StdEncoding.EncodeToString(value)
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: v})
}

func (e *encoder) AddByteString(key string, value []byte) {
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: string(value)})
}

func (e *encoder) AddBool(key string, value bool) {
	e.entries.boolEntries = append(e.entries.boolEntries, BoolEntry{Key: addKey(e.key, key), Value: value})
}

func (e *encoder) AddComplex128(key string, value complex128) {
	v := strconv.FormatComplex(value, 'f', -1, 128)
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: v})
}

func (e *encoder) AddComplex64(key string, value complex64) {
	v := strconv.FormatComplex(complex128(value), 'f', -1, 64)
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: v})
}

func (e *encoder) AddDuration(key string, value time.Duration) {
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: value.String()})
}

func (e *encoder) AddFloat64(key string, value float64) {
	e.entries.floatEntries = append(e.entries.floatEntries, FloatEntry{Key: addKey(e.key, key), Value: value})
}

func (e *encoder) AddFloat32(key string, value float32) {
	e.entries.floatEntries = append(e.entries.floatEntries, FloatEntry{Key: addKey(e.key, key), Value: float64(value)})
}

func (e *encoder) AddInt(key string, value int) {
	addInt64(e.entries, addKey(e.key, key), int64(value))
}

func (e *encoder) AddInt64(key string, value int64) {
	addInt64(e.entries, addKey(e.key, key), value)
}

func (e *encoder) AddInt32(key string, value int32) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addKey(e.key, key), Value: value})
}

func (e *encoder) AddInt16(key string, value int16) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addKey(e.key, key), Value: int32(value)})
}

func (e *encoder) AddInt8(key string, value int8) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addKey(e.key, key), Value: int32(value)})
}

func (e *encoder) AddString(key string, value string) {
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: value})
}

func (e *encoder) AddTime(key string, value time.Time) {
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: value.Format(time.RFC3339Nano)})
}

func (e *encoder) AddUint(key string, value uint) {
	addUint64(e.entries, addKey(e.key, key), uint64(value))
}

func (e *encoder) AddUint64(key string, value uint64) {
	addUint64(e.entries, addKey(e.key, key), value)
}

func (e *encoder) AddUint32(key string, value uint32) {
	addUint64(e.entries, addKey(e.key, key), uint64(value))
}

func (e *encoder) AddUint16(key string, value uint16) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addKey(e.key, key), Value: int32(value)})
}

func (e *encoder) AddUint8(key string, value uint8) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addKey(e.key, key), Value: int32(value)})
}

func (e *encoder) AddUintptr(key string, value uintptr) {
	addUint64(e.entries, addKey(e.key, key), uint64(value))
}

func (e *encoder) AddReflected(key string, value any) error {
	v := fmt.Sprint(value)
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: v})
	return nil
}

func (e *encoder) OpenNamespace(key string) {
	e.key = addKey(e.key, key)
}

func (e *encoder) Clone() zapcore.Encoder {
	return &encoder{
		key:              e.key,
		category:         e.category,
		formatCallerPath: e.formatCallerPath,
		entries: &recordEntries{
			stringEntries: e.entries.stringEntries,
			floatEntries:  e.entries.floatEntries,
			intEntries:    e.entries.intEntries,
			boolEntries:   e.entries.boolEntries,
		},
	}
}

func addKey(parent, child string) string {
	if parent == "" {
		return child
	}
	return parent + "." + child
}

func addIndex(parent string, index int) string {
	return fmt.Sprintf("%s[%d]", parent, index)
}

type objectEncoder struct {
	key     string
	entries *recordEntries
}

func (e *objectEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error {
	return marshaler.MarshalLogArray(&sliceEncoder{key: addKey(e.key, key), entries: e.entries})
}

func (e *objectEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error {
	return marshaler.MarshalLogObject(&objectEncoder{key: addKey(e.key, key), entries: e.entries})
}

// 不支持纯二进制类型，用 Base64 编码一下。
func (e *objectEncoder) AddBinary(key string, value []byte) {
	v := base64.StdEncoding.EncodeToString(value)
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: v})
}

func (e *objectEncoder) AddByteString(key string, value []byte) {
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: string(value)})
}

func (e *objectEncoder) AddBool(key string, value bool) {
	e.entries.boolEntries = append(e.entries.boolEntries, BoolEntry{Key: addKey(e.key, key), Value: value})
}

func (e *objectEncoder) AddComplex128(key string, value complex128) {
	v := strconv.FormatComplex(value, 'f', -1, 128)
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: v})
}

func (e *objectEncoder) AddComplex64(key string, value complex64) {
	v := strconv.FormatComplex(complex128(value), 'f', -1, 64)
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: v})
}

func (e *objectEncoder) AddDuration(key string, value time.Duration) {
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: value.String()})
}

func (e *objectEncoder) AddFloat64(key string, value float64) {
	e.entries.floatEntries = append(e.entries.floatEntries, FloatEntry{Key: addKey(e.key, key), Value: value})
}

func (e *objectEncoder) AddFloat32(key string, value float32) {
	e.entries.floatEntries = append(e.entries.floatEntries, FloatEntry{Key: addKey(e.key, key), Value: float64(value)})
}

func (e *objectEncoder) AddInt(key string, value int) {
	addInt64(e.entries, addKey(e.key, key), int64(value))
}

func (e *objectEncoder) AddInt64(key string, value int64) {
	addInt64(e.entries, addKey(e.key, key), value)
}

func (e *objectEncoder) AddInt32(key string, value int32) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addKey(e.key, key), Value: value})
}

func (e *objectEncoder) AddInt16(key string, value int16) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addKey(e.key, key), Value: int32(value)})
}

func (e *objectEncoder) AddInt8(key string, value int8) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addKey(e.key, key), Value: int32(value)})
}

func (e *objectEncoder) AddString(key string, value string) {
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: value})
}

func (e *objectEncoder) AddTime(key string, value time.Time) {
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: value.Format(time.RFC3339Nano)})
}

func (e *objectEncoder) AddUint(key string, value uint) {
	addUint64(e.entries, addKey(e.key, key), uint64(value))
}

func (e *objectEncoder) AddUint64(key string, value uint64) {
	addUint64(e.entries, addKey(e.key, key), value)
}

func (e *objectEncoder) AddUint32(key string, value uint32) {
	addUint64(e.entries, addKey(e.key, key), uint64(value))
}

func (e *objectEncoder) AddUint16(key string, value uint16) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addKey(e.key, key), Value: int32(value)})
}

func (e *objectEncoder) AddUint8(key string, value uint8) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addKey(e.key, key), Value: int32(value)})
}

func (e *objectEncoder) AddUintptr(key string, value uintptr) {
	addUint64(e.entries, addKey(e.key, key), uint64(value))
}

func (e *objectEncoder) AddReflected(key string, value any) error {
	v := fmt.Sprint(value)
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addKey(e.key, key), Value: v})
	return nil
}

func (e *objectEncoder) OpenNamespace(key string) {
	e.key = addKey(e.key, key)
}

type sliceEncoder struct {
	key     string
	index   int
	entries *recordEntries
}

func (e *sliceEncoder) AppendBool(value bool) {
	e.entries.boolEntries = append(e.entries.boolEntries, BoolEntry{Key: addIndex(e.key, e.index), Value: value})
	e.index++
}

func (e *sliceEncoder) AppendByteString(value []byte) {
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addIndex(e.key, e.index), Value: string(value)})
	e.index++
}

func (e *sliceEncoder) AppendComplex128(value complex128) {
	v := strconv.FormatComplex(value, 'f', -1, 128)
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addIndex(e.key, e.index), Value: v})
	e.index++
}

func (e *sliceEncoder) AppendComplex64(value complex64) {
	v := strconv.FormatComplex(complex128(value), 'f', -1, 64)
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addIndex(e.key, e.index), Value: v})
	e.index++
}

func (e *sliceEncoder) AppendFloat64(value float64) {
	e.entries.floatEntries = append(e.entries.floatEntries, FloatEntry{Key: addIndex(e.key, e.index), Value: value})
	e.index++
}

func (e *sliceEncoder) AppendFloat32(value float32) {
	e.entries.floatEntries = append(e.entries.floatEntries, FloatEntry{Key: addIndex(e.key, e.index), Value: float64(value)})
	e.index++
}

func (e *sliceEncoder) AppendInt(value int) {
	addInt64(e.entries, addIndex(e.key, e.index), int64(value))
	e.index++
}

func (e *sliceEncoder) AppendInt64(value int64) {
	addInt64(e.entries, addIndex(e.key, e.index), value)
	e.index++
}

func (e *sliceEncoder) AppendInt32(value int32) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addIndex(e.key, e.index), Value: value})
	e.index++
}

func (e *sliceEncoder) AppendInt16(value int16) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addIndex(e.key, e.index), Value: int32(value)})
	e.index++
}

func (e *sliceEncoder) AppendInt8(value int8) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addIndex(e.key, e.index), Value: int32(value)})
	e.index++
}

func (e *sliceEncoder) AppendString(value string) {
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addIndex(e.key, e.index), Value: value})
	e.index++
}

func (e *sliceEncoder) AppendUint(value uint) {
	addUint64(e.entries, addIndex(e.key, e.index), uint64(value))
	e.index++
}

func (e *sliceEncoder) AppendUint64(value uint64) {
	addUint64(e.entries, addIndex(e.key, e.index), value)
	e.index++
}

func (e *sliceEncoder) AppendUint32(value uint32) {
	addUint64(e.entries, addIndex(e.key, e.index), uint64(value))
	e.index++
}

func (e *sliceEncoder) AppendUint16(value uint16) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addIndex(e.key, e.index), Value: int32(value)})
	e.index++
}

func (e *sliceEncoder) AppendUint8(value uint8) {
	e.entries.intEntries = append(e.entries.intEntries, IntEntry{Key: addIndex(e.key, e.index), Value: int32(value)})
	e.index++
}

func (e *sliceEncoder) AppendUintptr(value uintptr) {
	addUint64(e.entries, addIndex(e.key, e.index), uint64(value))
	e.index++
}

func (e *sliceEncoder) AppendDuration(value time.Duration) {
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addIndex(e.key, e.index), Value: value.String()})
	e.index++
}

func (e *sliceEncoder) AppendTime(value time.Time) {
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addIndex(e.key, e.index), Value: value.Format(time.RFC3339Nano)})
	e.index++
}

func (e *sliceEncoder) AppendArray(value zapcore.ArrayMarshaler) error {
	err := value.MarshalLogArray(&sliceEncoder{key: addIndex(e.key, e.index), entries: e.entries})
	e.index++
	return err
}

func (e *sliceEncoder) AppendObject(value zapcore.ObjectMarshaler) error {
	err := value.MarshalLogObject(&objectEncoder{key: addIndex(e.key, e.index), entries: e.entries})
	e.index++
	return err
}

func (e *sliceEncoder) AppendReflected(value any) error {
	v := fmt.Sprint(value)
	e.entries.stringEntries = append(e.entries.stringEntries, StringEntry{Key: addIndex(e.key, e.index), Value: v})
	e.index++
	return nil
}
