package log

import (
	"encoding/json"
	"io"
	"os"
	"sync"

	"code.justin.tv/creator-collab/log/errors"
	"github.com/rollbar/rollbar-go"
)

type prodLogEventWriter struct {
	w             io.Writer
	rollbarClient *rollbar.Client
	lock          *sync.Mutex
}

var _ logEventWriter = &prodLogEventWriter{}

func newProdLogEventWriter(w io.Writer, rollbarClient *rollbar.Client) *prodLogEventWriter {
	return &prodLogEventWriter{
		w:             w,
		rollbarClient: rollbarClient,
		lock:          &sync.Mutex{},
	}
}

func (p *prodLogEventWriter) log(logEvent *LogEvent) {
	if logEvent == nil {
		return
	}

	p.sendLogEventToRollbar(logEvent)
	p.writeEventToWriterAsJson(logEvent)
}

func (p *prodLogEventWriter) sendLogEventToRollbar(logEvent *LogEvent) {
	if p.rollbarClient == nil {
		return
	}
	if logEvent.Level == LevelDebug {
		return
	}

	if logEvent.Error == nil {
		p.rollbarClient.MessageWithExtras(rollbar.INFO, logEvent.Message, logEvent.Fields)
		return
	}

	mergedFields := p.extractFieldsForRollbar(logEvent)
	p.rollbarClient.ErrorWithExtras(rollbar.ERR, logEvent.Error, mergedFields)
}

func (p *prodLogEventWriter) writeEventToWriterAsJson(logEvent *LogEvent) {
	payload := getLogEventPayload(logEvent)
	jsonBytes, marshalErr := json.Marshal(payload)
	if marshalErr != nil {
		_, _ = io.WriteString(os.Stderr, marshalErr.Error())
		return
	}

	p.lock.Lock()
	defer p.lock.Unlock()
	_, _ = p.w.Write(jsonBytes)
	_, _ = io.WriteString(p.w, "\n")
}

func (p *prodLogEventWriter) extractFieldsForRollbar(logEvent *LogEvent) map[string]interface{} {
	fieldsList := []map[string]interface{}{
		logEvent.Fields,
	}
	for err := logEvent.Error; err != nil; err = errors.Cause(err) {
		fields := errors.GetFields(err)
		fieldsList = append(fieldsList, fields)
	}

	// merge the key/value pairs into mergedFields, giving precedence to top level fields, and errors that
	// are higher in the cause chain.
	mergedFields := make(map[string]interface{})
	for _, fields := range fieldsList {
		for k, v := range fields {
			if _, ok := mergedFields[k]; !ok {
				mergedFields[k] = v
			}
		}
	}

	return mergedFields
}
