package segmentanalytics

import (
	"context"
	"log/slog"

	"github.com/SigNoz/signoz/pkg/analytics"
	"github.com/SigNoz/signoz/pkg/errors"
	"github.com/SigNoz/signoz/pkg/factory"
	"github.com/SigNoz/signoz/pkg/types/analyticstypes"
	segment "github.com/segmentio/analytics-go/v3"
)

type provider struct {
	settings factory.ScopedProviderSettings
	client   segment.Client
	stopC    chan struct{}
}

func NewFactory() factory.ProviderFactory[analytics.Analytics, analytics.Config] {
	return factory.NewProviderFactory(factory.MustNewName("segment"), New)
}

func New(ctx context.Context, providerSettings factory.ProviderSettings, config analytics.Config) (analytics.Analytics, error) {
	settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/analytics/segmentanalytics")

	client, err := segment.NewWithConfig(config.Segment.Key, segment.Config{
		Logger: newSegmentLogger(settings),
	})
	if err != nil {
		return nil, err
	}

	return &provider{
		settings: settings,
		client:   client,
		stopC:    make(chan struct{}),
	}, nil
}

func (provider *provider) Start(_ context.Context) error {
	<-provider.stopC
	return nil
}

func (provider *provider) Send(ctx context.Context, messages ...analyticstypes.Message) {
	for _, message := range messages {
		err := provider.client.Enqueue(message)
		if err != nil {
			provider.settings.Logger().WarnContext(ctx, "unable to send message to segment", errors.Attr(err))
		}
	}
}

func (provider *provider) TrackGroup(ctx context.Context, group, event string, properties map[string]any) {
	if properties == nil {
		provider.settings.Logger().WarnContext(ctx, "empty attributes received, skipping event", slog.String("group", group), slog.String("event", event))
		return
	}

	err := provider.client.Enqueue(analyticstypes.Track{
		UserId:     "stats_" + group,
		Event:      event,
		Properties: analyticstypes.NewPropertiesFromMap(properties),
		Context: &analyticstypes.Context{
			Extra: map[string]interface{}{
				analyticstypes.KeyGroupID: group,
			},
		},
	})
	if err != nil {
		provider.settings.Logger().WarnContext(ctx, "unable to send message to segment", errors.Attr(err))
	}
}

func (provider *provider) TrackUser(ctx context.Context, group, user, event string, properties map[string]any) {
	if properties == nil {
		provider.settings.Logger().WarnContext(ctx, "empty attributes received, skipping event", slog.String("user", user), slog.String("group", group), slog.String("event", event))
		return
	}

	err := provider.client.Enqueue(analyticstypes.Track{
		UserId:     user,
		Event:      event,
		Properties: analyticstypes.NewPropertiesFromMap(properties),
		Context: &analyticstypes.Context{
			Extra: map[string]interface{}{
				analyticstypes.KeyGroupID: group,
			},
		},
	})
	if err != nil {
		provider.settings.Logger().WarnContext(ctx, "unable to send message to segment", errors.Attr(err))
	}
}

func (provider *provider) IdentifyGroup(ctx context.Context, group string, traits map[string]any) {
	if traits == nil {
		provider.settings.Logger().WarnContext(ctx, "empty attributes received, skipping identify", slog.String("group", group))
		return
	}

	// identify the user
	err := provider.client.Enqueue(analyticstypes.Identify{
		UserId: "stats_" + group,
		Traits: analyticstypes.NewTraitsFromMap(traits),
	})
	if err != nil {
		provider.settings.Logger().WarnContext(ctx, "unable to send message to segment", errors.Attr(err))
	}

	// identify the group using the stats user
	err = provider.client.Enqueue(analyticstypes.Group{
		UserId:  "stats_" + group,
		GroupId: group,
		Traits:  analyticstypes.NewTraitsFromMap(traits),
	})
	if err != nil {
		provider.settings.Logger().WarnContext(ctx, "unable to send message to segment", errors.Attr(err))
	}
}

func (provider *provider) IdentifyUser(ctx context.Context, group, user string, traits map[string]any) {
	if traits == nil {
		provider.settings.Logger().WarnContext(ctx, "empty attributes received, skipping identify", slog.String("user", user), slog.String("group", group))
		return
	}

	// identify the user
	err := provider.client.Enqueue(analyticstypes.Identify{
		UserId: user,
		Traits: analyticstypes.NewTraitsFromMap(traits),
	})
	if err != nil {
		provider.settings.Logger().WarnContext(ctx, "unable to send message to segment", errors.Attr(err))
	}

	// associate the user with the group
	err = provider.client.Enqueue(analyticstypes.Group{
		UserId:  user,
		GroupId: group,
		Traits:  analyticstypes.NewTraits().Set("id", group), // A trait is required
	})
	if err != nil {
		provider.settings.Logger().WarnContext(ctx, "unable to send message to segment", errors.Attr(err))
	}
}

func (provider *provider) Stop(ctx context.Context) error {
	if err := provider.client.Close(); err != nil {
		provider.settings.Logger().WarnContext(ctx, "unable to close segment client", errors.Attr(err))
	}

	close(provider.stopC)
	return nil
}
