// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

package jsonselect

import (
	"bytes"
	"fmt"
	"strings"

	"github.com/buger/jsonparser"
	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
	"github.com/caddyserver/caddy/v2/modules/logging"
	"go.uber.org/zap/buffer"
	"go.uber.org/zap/zapcore"
)

const (
	moduleName = "jsonselect"
	moduleID   = "caddy.logging.encoders." + moduleName
)

func init() {
	caddy.RegisterModule(JSONSelectEncoder{})
}

type JSONSelectEncoder struct {
	logging.LogEncoderConfig
	zapcore.Encoder `json:"-"`
	Selector        string `json:"selector,omitempty"`

	getters [][]string
	setters [][]string
}

func (JSONSelectEncoder) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID: moduleID,
		New: func() caddy.Module {
			return &JSONSelectEncoder{
				Encoder: new(logging.JSONEncoder),
			}
		},
	}
}

func (e *JSONSelectEncoder) Provision(ctx caddy.Context) error {
	if e.Selector == "" {
		return fmt.Errorf("selector is mandatory")
	}

	e.setters = [][]string{}
	e.getters = [][]string{}
	r := caddy.NewReplacer()
	r.Map(func(sel string) (interface{}, bool) {
		var set, get string

		parts := strings.Split(sel, ":")
		if len(parts) == 1 {
			set = parts[0]
			get = set
		} else if len(parts) == 2 {
			set = parts[0]
			get = parts[1]
		} else {
			// todo > error out - how?
			return nil, false
		}

		e.setters = append(e.setters, strings.Split(set, ">"))
		e.getters = append(e.getters, strings.Split(get, ">"))
		return nil, false
	})
	r.ReplaceAll(e.Selector, "")

	if len(e.setters) != len(e.getters) {
		return fmt.Errorf("selector must have the same number of setters and getters")
	}

	e.Encoder = zapcore.NewJSONEncoder(e.ZapcoreEncoderConfig())
	return nil
}

func (e JSONSelectEncoder) Clone() zapcore.Encoder {
	return JSONSelectEncoder{
		LogEncoderConfig: e.LogEncoderConfig,
		Encoder:          e.Encoder.Clone(),
		Selector:         e.Selector,
		getters:          e.getters,
		setters:          e.setters,
	}
}

func (e JSONSelectEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
	buf, err := e.Encoder.EncodeEntry(entry, fields)
	if err != nil {
		return buf, err
	}

	res := []byte{'{', '}'}
	// Temporary workaround the bug https://github.com/buger/jsonparser/issues/232
	// TODO(leo): switch back to EachKey (see git history) for perf reasons when fixed
	for idx, paths := range e.getters {
		val, typ, _, err := jsonparser.Get(buf.Bytes(), paths...)
		if err == jsonparser.KeyPathNotFoundError {
			// path not found, skip
			continue
		}
		if err != nil {
			return nil, err
		}
		switch typ {
		case jsonparser.NotExist:
			// path not found, skip
		case jsonparser.String:
			res, _ = jsonparser.Set(res, append(append([]byte{'"'}, val...), '"'), e.setters[idx]...)
		default:
			res, _ = jsonparser.Set(res, val, e.setters[idx]...)
		}
	}

	// Reset the buffer to output our own content
	buf.Reset()
	// Insert the new content
	nl := []byte("\n")
	if !bytes.HasSuffix(res, nl) {
		res = append(res, nl...)
	}
	buf.Write(res)

	return buf, err
}

// Interface guards
var (
	_ zapcore.Encoder       = (*JSONSelectEncoder)(nil)
	_ caddy.Provisioner     = (*JSONSelectEncoder)(nil)
	_ caddyfile.Unmarshaler = (*JSONSelectEncoder)(nil)
)
