// Copyright IBM Corp. 2020, 2026
// SPDX-License-Identifier: BUSL-1.1

package event

import (
	"fmt"

	"github.com/hashicorp/eventlogger"
	"github.com/hashicorp/eventlogger/filters/gated"
	"github.com/hashicorp/go-secure-stdlib/strutil"
)

// observationVersion defines the version of observation events
const observationVersion = "v0.1"

type observation struct {
	Version     string         `json:"version"`
	Op          Op             `json:"op,omitempty"`
	RequestInfo *RequestInfo   `json:"request_info,omitempty"`
	ID          string         `json:"-"`
	Flush       bool           `json:"-"`
	Header      map[string]any `json:"header,omitempty"`
	Detail      map[string]any `json:"detail,omitempty"`
	Request     *Request       `json:"request,omitempty"`
	Response    *Response      `json:"response,omitempty"`
}

func newObservation(fromOperation Op, opt ...Option) (*observation, error) {
	const op = "event.newObservation"
	if fromOperation == "" {
		return nil, fmt.Errorf("%s: missing operation: %w", op, ErrInvalidParameter)
	}
	opts := getOpts(opt...)
	if opts.withId == "" {
		var err error
		opts.withId, err = NewId(string(ObservationType))
		if err != nil {
			return nil, fmt.Errorf("%s: %w", op, err)
		}
	}
	for k := range opts.withHeader {
		if strutil.StrListContains([]string{OpField, VersionField, RequestInfoField}, k) {
			return nil, fmt.Errorf("%s: %s is a reserved field name: %w", op, k, ErrInvalidParameter)
		}
	}
	i := &observation{
		ID:          opts.withId,
		Header:      opts.withHeader,
		Detail:      opts.withDetails,
		Flush:       opts.withFlush,
		Op:          fromOperation,
		RequestInfo: opts.withRequestInfo,
		Version:     observationVersion,
	}

	if opts.withTelemetry {
		i.Request = opts.withRequest
		i.Response = opts.withResponse
	}

	if err := i.validate(); err != nil {
		return nil, fmt.Errorf("%s: %w", op, err)
	}
	return i, nil
}

// EventType is required for all event types by the eventlogger broker
func (o *observation) EventType() string { return string(ObservationType) }

func (o *observation) validate() error {
	const op = "event.(Observation).validate"
	if o.ID == "" {
		return fmt.Errorf("%s: missing id: %w", op, ErrInvalidParameter)
	}
	if o.Op == "" {
		return fmt.Errorf("%s: missing operation: %w", op, ErrInvalidParameter)
	}
	return nil
}

// ComposedFrom will build a single event payload which will be
// Flushed/Processed from a collection of gated observation events.  The payload
// returned is  not a Gateable payload intentionally.  Note: the receiver is
// always nil when this function is called.
func (o *observation) ComposeFrom(events []*eventlogger.Event) (eventlogger.EventType, any, error) {
	const op = "event.(observation).ComposedFrom"
	if len(events) == 0 {
		return "", nil, fmt.Errorf("%s: missing events: %w", op, eventlogger.ErrInvalidParameter)
	}

	payload := map[string]any{}
	for i, v := range events {
		g, ok := v.Payload.(*observation)
		if !ok {
			return "", nil, fmt.Errorf("%s: event %d is not an observation: %w", op, i, eventlogger.ErrInvalidParameter)
		}
		if g.Header != nil {
			for hdrK, hdrV := range g.Header {
				payload[hdrK] = hdrV
			}
		}
		if g.RequestInfo != nil {
			payload[RequestInfoField] = g.RequestInfo
		}
		if g.Detail != nil {
			if _, ok := payload[DetailsField]; !ok {
				payload[DetailsField] = []gated.EventPayloadDetails{}
			}
			payload[DetailsField] = append(payload[DetailsField].([]gated.EventPayloadDetails), gated.EventPayloadDetails{
				Type:      string(g.Op),
				CreatedAt: v.CreatedAt.String(),
				Payload:   g.Detail,
			})
		}
		if g.Request != nil {
			msgReq := &Request{}
			if v, ok := payload[RequestField]; ok {
				msgReq, ok = v.(*Request)
				if !ok {
					return "", nil, fmt.Errorf("%s: request %d is not an observation request: %w", op, i, eventlogger.ErrInvalidParameter)
				}
			}
			if g.Request.Details != nil {
				filteredRequest, err := filterProtoMessage(g.Request.Details, telemetryFilter)
				if err != nil {
					continue
				}
				msgReq.Details = filteredRequest
			}
			if g.Request.Operation != "" {
				msgReq.Operation = g.Request.Operation
			}
			if g.Request.Endpoint != "" {
				msgReq.Endpoint = g.Request.Endpoint
			}
			if g.Request.DetailsUpstreamMessage != nil {
				msgReq.DetailsUpstreamMessage = g.Request.DetailsUpstreamMessage
			}
			if g.Request.UserAgents != nil {
				msgReq.UserAgents = g.Request.UserAgents
			}
			payload[RequestField] = msgReq
		}
		if g.Response != nil {
			msgRes := &Response{}
			if v, ok := payload[ResponseField]; ok {
				msgRes, ok = v.(*Response)
				if !ok {
					return "", nil, fmt.Errorf("%s: response %d is not an observation response: %w", op, i, eventlogger.ErrInvalidParameter)
				}
			}
			if g.Response.StatusCode != 0 {
				msgRes.StatusCode = g.Response.StatusCode
			}
			if g.Response.Details != nil {
				filteredResponse, err := filterProtoMessage(g.Response.Details, telemetryFilter)
				if err != nil {
					continue
				}
				msgRes.Details = filteredResponse
			}
			if g.Response.DetailsUpstreamMessage != nil {
				msgRes.DetailsUpstreamMessage = g.Response.DetailsUpstreamMessage
			}
			payload[ResponseField] = msgRes
		}
	}
	return events[0].Type, payload, nil
}

var _ gated.Gateable = &observation{}

// GetID returns the unique id used for gating
func (o *observation) GetID() string {
	return o.ID
}

// FlushEvent tells the Filter to flush/process the events associated with
// the Gateable ID
func (o *observation) FlushEvent() bool {
	return o.Flush
}
