package ldclient

import (
	"github.com/launchdarkly/go-sdk-common/v3/ldcontext"
	"github.com/launchdarkly/go-sdk-common/v3/ldreason"
	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
	ldevents "github.com/launchdarkly/go-sdk-events/v2"
	ldeval "github.com/launchdarkly/go-server-sdk-evaluation/v2"
	"github.com/launchdarkly/go-server-sdk/v6/interfaces"
	"github.com/launchdarkly/go-server-sdk/v6/interfaces/flagstate"
	"github.com/launchdarkly/go-server-sdk/v6/ldcomponents"
	"github.com/launchdarkly/go-server-sdk/v6/subsystems"
)

// This file contains internal support code for LDClient's interactions with the analytics event pipeline.
//
// General implementation notes:
//
// Under normal circumstances, an analytics event is generated whenever 1. a flag is evaluated explicitly
// with a Variation method, 2. a flag is evaluated indirectly as a prerequisite, or 3. the application
// explicitly generates an event by calling Identify or Track. This event is submitted to the configured
// EventProcessor's SendEvent method; the EventProcessor then does any necessary processing and eventually
// delivers the event data to LaunchDarkly, either as a full event or in summary data. The implementation
// of that logic is all in go-sdk-events (since it can be used outside of the SDK, as in ld-relay).
//
// When events are disabled, the EventProcessor is a null implementation that does nothing. It is safe to
// use that object, but LDClient still refrains from doing so if it knows events are disabled so that we
// can avoid a little bit of computational overhead. That's the reason for the IsNullEventProcessorFactory
// method.

type nullEventProcessorFactoryDescription interface {
	IsNullEventProcessorFactory() bool
}

func isNullEventProcessorFactory(f subsystems.ComponentConfigurer[ldevents.EventProcessor]) bool {
	if nf, ok := f.(nullEventProcessorFactoryDescription); ok {
		return nf.IsNullEventProcessorFactory()
	}
	return false
}

func getEventProcessorFactory(config Config) subsystems.ComponentConfigurer[ldevents.EventProcessor] {
	if config.Offline {
		return ldcomponents.NoEvents()
	}
	if config.Events == nil {
		return ldcomponents.SendEvents()
	}
	return config.Events
}

// This struct is used during evaluations to keep track of the event generation strategy we are using
// (with or without evaluation reasons). It captures all of the relevant state so that we do not need to
// create any more stateful objects, such as closures, to generate events during an evaluation. See
// CONTRIBUTING.md for performance issues with closures.
type eventsScope struct {
	disabled                  bool
	factory                   ldevents.EventFactory
	prerequisiteEventRecorder ldeval.PrerequisiteFlagEventRecorder
}

func newDisabledEventsScope() eventsScope {
	return eventsScope{disabled: true}
}

func newEventsScope(client *LDClient, withReasons bool) eventsScope {
	factory := ldevents.NewEventFactory(withReasons, nil)
	return eventsScope{
		factory: factory,
		prerequisiteEventRecorder: func(params ldeval.PrerequisiteFlagEvent) {
			client.eventProcessor.RecordEvaluation(factory.NewEvaluationData(
				ldevents.FlagEventProperties{
					Key:                  params.PrerequisiteFlag.Key,
					Version:              params.PrerequisiteFlag.Version,
					RequireFullEvent:     params.PrerequisiteFlag.TrackEvents,
					DebugEventsUntilDate: params.PrerequisiteFlag.DebugEventsUntilDate,
				},
				ldevents.Context(params.Context),
				params.PrerequisiteResult.Detail,
				params.PrerequisiteResult.IsExperiment,
				ldvalue.Null(),
				params.TargetFlagKey,
			))
		},
	}
}

// This implementation of interfaces.LDClientInterface delegates all client operations to the
// underlying LDClient, but suppresses the generation of analytics events.
type clientEventsDisabledDecorator struct {
	client *LDClient
	scope  eventsScope
}

func newClientEventsDisabledDecorator(client *LDClient) interfaces.LDClientInterface {
	return &clientEventsDisabledDecorator{client: client, scope: newDisabledEventsScope()}
}

func (c *clientEventsDisabledDecorator) BoolVariation(
	key string,
	context ldcontext.Context,
	defaultVal bool,
) (bool, error) {
	detail, err := c.client.variation(key, context, ldvalue.Bool(defaultVal), true, c.scope)
	return detail.Value.BoolValue(), err
}

func (c *clientEventsDisabledDecorator) BoolVariationDetail(key string, context ldcontext.Context, defaultVal bool) (
	bool, ldreason.EvaluationDetail, error) {
	detail, err := c.client.variation(key, context, ldvalue.Bool(defaultVal), true, c.scope)
	return detail.Value.BoolValue(), detail, err
}

func (c *clientEventsDisabledDecorator) IntVariation(
	key string,
	context ldcontext.Context,
	defaultVal int,
) (int, error) {
	detail, err := c.client.variation(key, context, ldvalue.Int(defaultVal), true, c.scope)
	return detail.Value.IntValue(), err
}

func (c *clientEventsDisabledDecorator) IntVariationDetail(key string, context ldcontext.Context, defaultVal int) (
	int, ldreason.EvaluationDetail, error) {
	detail, err := c.client.variation(key, context, ldvalue.Int(defaultVal), true, c.scope)
	return detail.Value.IntValue(), detail, err
}

func (c *clientEventsDisabledDecorator) Float64Variation(key string, context ldcontext.Context, defaultVal float64) (
	float64, error) {
	detail, err := c.client.variation(key, context, ldvalue.Float64(defaultVal), true, c.scope)
	return detail.Value.Float64Value(), err
}

func (c *clientEventsDisabledDecorator) Float64VariationDetail(
	key string,
	context ldcontext.Context,
	defaultVal float64,
) (
	float64, ldreason.EvaluationDetail, error) {
	detail, err := c.client.variation(key, context, ldvalue.Float64(defaultVal), true, c.scope)
	return detail.Value.Float64Value(), detail, err
}

func (c *clientEventsDisabledDecorator) StringVariation(key string, context ldcontext.Context, defaultVal string) (
	string, error) {
	detail, err := c.client.variation(key, context, ldvalue.String(defaultVal), true, c.scope)
	return detail.Value.StringValue(), err
}

func (c *clientEventsDisabledDecorator) StringVariationDetail(
	key string,
	context ldcontext.Context,
	defaultVal string,
) (
	string, ldreason.EvaluationDetail, error) {
	detail, err := c.client.variation(key, context, ldvalue.String(defaultVal), true, c.scope)
	return detail.Value.StringValue(), detail, err
}

func (c *clientEventsDisabledDecorator) JSONVariation(key string, context ldcontext.Context, defaultVal ldvalue.Value) (
	ldvalue.Value, error) {
	detail, err := c.client.variation(key, context, defaultVal, true, c.scope)
	return detail.Value, err
}

func (c *clientEventsDisabledDecorator) JSONVariationDetail(
	key string,
	context ldcontext.Context,
	defaultVal ldvalue.Value,
) (
	ldvalue.Value, ldreason.EvaluationDetail, error) {
	detail, err := c.client.variation(key, context, defaultVal, true, c.scope)
	return detail.Value, detail, err
}

func (c *clientEventsDisabledDecorator) AllFlagsState(
	context ldcontext.Context,
	options ...flagstate.Option,
) flagstate.AllFlags {
	// Currently AllFlagsState never generates events anyway, so nothing is different here
	return c.client.AllFlagsState(context, options...)
}

func (c *clientEventsDisabledDecorator) Identify(context ldcontext.Context) error {
	return nil
}

func (c *clientEventsDisabledDecorator) TrackEvent(eventName string, context ldcontext.Context) error {
	return nil
}

func (c *clientEventsDisabledDecorator) TrackData(
	eventName string,
	context ldcontext.Context,
	data ldvalue.Value,
) error {
	return nil
}

func (c *clientEventsDisabledDecorator) TrackMetric(eventName string, context ldcontext.Context, metricValue float64,
	data ldvalue.Value) error {
	return nil
}

func (c *clientEventsDisabledDecorator) WithEventsDisabled(disabled bool) interfaces.LDClientInterface {
	if disabled {
		return c
	}
	return c.client
}
