package parser

import (
	"encoding/json"
	"time"

	"code.justin.tv/creator-collab/log"
	"code.justin.tv/creator-collab/log/errors"
	"github.com/aws/aws-lambda-go/events"
)

type Parser struct {
	logger log.Logger
}

type LogEvent struct {
	LogEventID  string
	Timestamp   time.Time
	Message     string
	Raw         string
	AccountID   string
	LogGroup    string
	LogStream   string
	Fingerprint string

	// Optional
	StackTrace []StackTrace
	RequestID  string
	Operation  string

	// Implement later
	Level  string
	Fields map[string]string
}

type messageProperties struct {
	message     string
	fingerprint string
	stackTrace  []StackTrace
	requestID   string
	operation   string

	level  string
	fields map[string]string
}

type StackTrace struct {
	FullFilePath string
	Method       string
	Line         int32

	// Brazil specific properties
	ShortFilePath     string
	BrazilPackageName string
	Relevant          bool
	LineURL           string
	PackageURL        string
}

func (p *Parser) Parse(logsData *events.CloudwatchLogsData) ([]LogEvent, error) {
	logEvents := make([]LogEvent, 0, len(logsData.LogEvents))

	accountID := logsData.Owner
	logGroup := logsData.LogGroup
	logStream := logsData.LogStream

	for _, cwlLogEvent := range logsData.LogEvents {
		if cwlLogEvent.Timestamp == 0 {
			err := newParseError("timestamp was 0", &cwlLogEvent, logGroup)
			p.logParseFailure(err, logGroup)
			continue
		}
		timestamp := parseUnixMilliseconds(cwlLogEvent.Timestamp)

		msgProps, err := p.parseMessage(cwlLogEvent.Message)
		if err != nil {
			p.logParseFailure(err, logGroup)
			continue
		}

		logEvent := LogEvent{
			AccountID:   accountID,
			Fields:      msgProps.fields,
			Fingerprint: msgProps.fingerprint,
			Level:       msgProps.level,
			LogEventID:  cwlLogEvent.ID, // TODO: Review whether this ID is suitable
			LogGroup:    logGroup,
			LogStream:   logStream,
			Message:     msgProps.message,
			Operation:   msgProps.operation,
			Raw:         cwlLogEvent.Message,
			RequestID:   msgProps.requestID,
			StackTrace:  msgProps.stackTrace,
			Timestamp:   timestamp,
		}
		logEvents = append(logEvents, logEvent)
	}

	return logEvents, nil
}

func newParseError(reason string, cwlLogEvent *events.CloudwatchLogsLogEvent, logGroup string) error {
	return errors.New("parsing CloudwatchLogsLogEvent failed", errors.Fields{
		"reason":                 reason,
		"log_group":              logGroup,
		"log_event_id":           cwlLogEvent.ID,
		"log_event_timestamp_ms": cwlLogEvent.Timestamp,
		"log_event_message":      cwlLogEvent.Message,
	})
}

func (p *Parser) logParseFailure(err error, logGroup string) {
	p.logger.Error(err)
	// TODO: Record metric on the failure
}

func (p *Parser) parseMessage(cwlMsg string) (*messageProperties, error) {
	jsonObj := make(map[string]interface{}, 0)
	err := json.Unmarshal([]byte(cwlMsg), &jsonObj)
	if err != nil {
		return parsePlainTextPayload(cwlMsg)
	}

	return parseJsonPayload(jsonObj)
}

func parseUnixMilliseconds(n int64) time.Time {
	unixNano := n * 1000000
	return time.Unix(0, unixNano).UTC()
}
