package logstream

import (
	"context"
	"io"
	"net/http"
	"time"

	"github.com/moby/moby/v2/daemon/server/backend"
	"github.com/moby/moby/v2/daemon/server/httputils"
	"github.com/moby/moby/v2/pkg/ioutils"
)

// WriteJSON writes a JSON stream of log messages from the messages channel.
func WriteJSON(ctx context.Context, w http.ResponseWriter, msgs <-chan *backend.LogMessage, config *backend.ContainerLogsOptions) {
	// See https://github.com/moby/moby/issues/47448
	// Trigger headers to be written immediately.
	w.WriteHeader(http.StatusOK)

	wf := ioutils.NewWriteFlusher(w)
	defer wf.Close()

	wf.Flush()

	jsonWriter := newJSONLogWriter(wf, w.Header().Get("Content-Type"), config)
	for {
		select {
		case <-ctx.Done():
			return
		case msg, ok := <-msgs:
			if !ok {
				return
			}
			if msg.Err != nil {
				// message contains an error; write the error and continue
				jsonWriter.write(msg)
				continue
			}
			switch msg.Source {
			case "stdout":
				if config.ShowStdout {
					jsonWriter.write(msg)
				}
			case "stderr":
				if config.ShowStderr {
					jsonWriter.write(msg)
				}
			default:
				// unknown source
			}
		}
	}
}

type jsonLogWriter struct {
	encode  httputils.EncoderFn
	details bool
}

func newJSONLogWriter(w io.Writer, contentType string, opts *backend.ContainerLogsOptions) *jsonLogWriter {
	encode := httputils.NewJSONStreamEncoder(w, contentType)
	return &jsonLogWriter{
		encode:  encode,
		details: opts != nil && opts.Details,
	}
}

// jsonLogMessage represents a single log entry in JSON log streaming format.
//
// Each message is serialized as a standalone JSON object and emitted as
// part of a stream (one object per line) in container log responses when
// a JSON-formatted output is requested.
//
// TODO(thaJeztah): move to the api module and generate from swagger.
type jsonLogMessage struct {
	// Line contains the log payload as UTF-8 text when text encoding is used.
	// When an alternate encoding is requested, this field is omitted.
	Line string `json:"Line,omitempty"`

	// Source identifies the originating stream ("stdout" or "stderr").
	Source string `json:"Source,omitempty"`

	// Timestamp is the time at which the log record was produced,
	// encoded in RFC3339Nano format.
	Timestamp time.Time `json:"Timestamp,omitempty"`

	// Attrs contains optional structured attributes when "details" is
	// enabled, and if supported by the logging driver in use.
	Attrs []backend.LogAttr `json:"Attrs,omitempty"`

	// MetaData contains metadata for partial log records that must be
	// reassembled by the client.
	MetaData *backend.PartialLogMetaData `json:"MetaData,omitempty"`

	// Error contains an associated error encountered while processing the
	// log message, if any.
	Error string `json:"Error,omitempty"`
}

func (w *jsonLogWriter) write(msg *backend.LogMessage) {
	var errMsg string
	if msg.Err != nil {
		errMsg = msg.Err.Error()
	}

	var attrs []backend.LogAttr
	if w.details {
		attrs = msg.Attrs
	}

	_ = w.encode(jsonLogMessage{
		Line:      string(msg.Line),
		Source:    msg.Source,
		Timestamp: msg.Timestamp,
		Attrs:     attrs,
		MetaData:  msg.PLogMetaData,
		Error:     errMsg,
	})
}
