package di

import (
	"context"
	"crypto/tls"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"cloud.google.com/go/compute/metadata"
	"github.com/NdoleStudio/httpsms/docs"
	plunk "github.com/NdoleStudio/plunk-go"
	"github.com/pusher/pusher-http-go/v5"

	otelMetric "go.opentelemetry.io/otel/metric"

	"github.com/dgraph-io/ristretto/v2"

	otelfiber "github.com/gofiber/contrib/v3/otel"
	"gorm.io/plugin/opentelemetry/tracing"

	"github.com/NdoleStudio/httpsms/pkg/discord"

	"cloud.google.com/go/storage"
	mexporter "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric"
	cloudtrace "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace"
	"github.com/NdoleStudio/httpsms/pkg/cache"
	"github.com/NdoleStudio/lemonsqueezy-go"
	"github.com/hashicorp/go-retryablehttp"
	"github.com/redis/go-redis/extra/redisotel/v9"
	"github.com/redis/go-redis/v9"
	"go.opentelemetry.io/otel/sdk/metric"

	"github.com/NdoleStudio/go-otelroundtripper"

	"github.com/uptrace/uptrace-go/uptrace"

	"github.com/NdoleStudio/httpsms/pkg/emails"

	cloudtasks "cloud.google.com/go/cloudtasks/apiv2"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/sdk/resource"
	semconv "go.opentelemetry.io/otel/semconv/v1.10.0"

	axiomzerolog "github.com/axiomhq/axiom-go/adapters/zerolog"
	"github.com/hirosassa/zerodriver"
	"github.com/rs/zerolog"
	"go.opentelemetry.io/otel/sdk/trace"

	firebase "firebase.google.com/go"
	"firebase.google.com/go/auth"
	"github.com/NdoleStudio/httpsms/pkg/middlewares"
	"google.golang.org/api/option"

	"github.com/gofiber/fiber/v3/middleware/compress"
	"github.com/gofiber/fiber/v3/middleware/cors"

	"github.com/NdoleStudio/httpsms/pkg/entities"
	"github.com/NdoleStudio/httpsms/pkg/listeners"
	"github.com/NdoleStudio/httpsms/pkg/repositories"
	"github.com/NdoleStudio/httpsms/pkg/services"
	swagger "github.com/gofiber/contrib/v3/swaggo"
	"github.com/gofiber/fiber/v3"
	fiberLogger "github.com/gofiber/fiber/v3/middleware/logger"
	"github.com/palantir/stacktrace"
	ttlCache "github.com/patrickmn/go-cache"
	"gorm.io/gorm"

	"github.com/NdoleStudio/httpsms/pkg/handlers"
	"github.com/NdoleStudio/httpsms/pkg/telemetry"
	"github.com/NdoleStudio/httpsms/pkg/validators"
	mongoDriver "go.mongodb.org/mongo-driver/v2/mongo"
	"gorm.io/driver/postgres"
	gormLogger "gorm.io/gorm/logger"
)

// Container is used to resolve services at runtime
type Container struct {
	projectID            string
	db                   *gorm.DB
	dedicatedDB          *gorm.DB
	mongoDB              *mongoDriver.Database
	version              string
	app                  *fiber.App
	eventDispatcher      *services.EventDispatcher
	logger               telemetry.Logger
	attachmentRepository repositories.AttachmentRepository
	userRistrettoCache   *ristretto.Cache[string, entities.AuthContext]
	phoneRistrettoCache  *ristretto.Cache[string, *entities.Phone]
	inMemoryCache        cache.Cache
}

// NewLiteContainer creates a Container without any routes or listeners
func NewLiteContainer() (container *Container) {
	return &Container{
		logger: logger(3).WithService(fmt.Sprintf("%T", container)),
	}
}

// NewContainer creates a new dependency injection container
func NewContainer(projectID string, version string) (container *Container) {
	container = &Container{
		projectID: projectID,
		version:   version,
		logger:    logger(3).WithService(fmt.Sprintf("%T", container)),
	}

	container.InitializeTraceProvider()

	container.RegisterMessageListeners()
	container.RegisterMessageRoutes()
	container.RegisterAttachmentRoutes()
	container.RegisterBulkMessageRoutes()

	container.RegisterMessageThreadRoutes()
	container.RegisterMessageThreadListeners()

	container.RegisterHeartbeatRoutes()
	container.RegisterHeartbeatListeners()

	container.RegisterUserRoutes()
	container.RegisterMessageSendScheduleRoutes()
	container.RegisterMessageSendScheduleListeners()
	container.RegisterUserListeners()

	container.RegisterPhoneRoutes()
	container.RegisterPhoneListeners()

	container.RegisterEventRoutes()

	container.RegisterNotificationListeners()
	container.RegisterEmailNotificationListeners()

	container.RegisterBillingRoutes()
	container.RegisterBillingListeners()

	container.RegisterWebhookRoutes()
	container.RegisterWebhookListeners()

	container.RegisterLemonsqueezyRoutes()

	container.RegisterIntegration3CXRoutes()
	container.RegisterIntegration3CXListeners()

	container.RegisterDiscordRoutes()
	container.RegisterDiscordListeners()

	container.RegisterPhoneAPIKeyRoutes()
	container.RegisterPhoneAPIKeyListeners()

	container.RegisterMarketingListeners()
	container.RegisterWebsocketListeners()

	// this has to be last since it registers the /* route
	container.RegisterSwaggerRoutes()

	return container
}

// App creates a new instance of fiber.App
func (container *Container) App() (app *fiber.App) {
	if container.app != nil {
		return container.app
	}

	container.logger.Debug(fmt.Sprintf("creating %T", app))

	app = fiber.New()

	// Health check endpoint registered before middleware for reliable Docker health checks
	app.Get("/health", func(c fiber.Ctx) error {
		return c.SendStatus(fiber.StatusOK)
	})

	app.Use(compress.New(compress.Config{
		Level: compress.LevelBestCompression,
	}))

	if os.Getenv("USE_HTTP_LOGGER") == "true" {
		app.Use(fiberLogger.New())
	}

	app.Use(otelfiber.Middleware())
	app.Use(
		cors.New(
			cors.Config{
				AllowOrigins:     splitCommaEnv("CORS_ALLOW_ORIGINS", "*"),
				AllowHeaders:     splitCommaEnv("CORS_ALLOW_HEADERS", "*"),
				AllowMethods:     splitCommaEnv("CORS_ALLOW_METHODS", "GET,POST,PUT,DELETE,OPTIONS"),
				AllowCredentials: false,
				ExposeHeaders:    splitCommaEnv("CORS_EXPOSE_HEADERS", "*"),
			},
		),
	)
	app.Use(middlewares.HTTPRequestLogger(container.Tracer(), container.Logger()))
	app.Use(middlewares.BearerAuth(container.Logger(), container.Tracer(), container.FirebaseAuthClient()))
	app.Use(middlewares.APIKeyAuth(container.Logger(), container.Tracer(), container.UserRepository()))

	container.app = app
	return app
}

// BearerAPIKeyMiddleware creates a new instance of middlewares.BearerAPIKeyAuth
func (container *Container) BearerAPIKeyMiddleware() fiber.Handler {
	container.logger.Debug("creating middlewares.BearerAPIKeyAuth")
	return middlewares.BearerAPIKeyAuth(container.Logger(), container.Tracer(), container.UserRepository())
}

// PhoneAPIKeyMiddleware creates a new instance of middlewares.BearerAPIKeyAuth
func (container *Container) PhoneAPIKeyMiddleware() fiber.Handler {
	container.logger.Debug("creating middlewares.PhoneAPIKeyMiddleware")
	return middlewares.PhoneAPIKeyAuth(container.Logger(), container.Tracer(), container.PhoneAPIKeyRepository())
}

// AuthenticatedMiddleware creates a new instance of middlewares.Authenticated
func (container *Container) AuthenticatedMiddleware() fiber.Handler {
	container.logger.Debug("creating middlewares.Authenticated")
	return middlewares.Authenticated(container.Tracer())
}

// Logger creates a new instance of telemetry.Logger
func (container *Container) Logger(skipFrameCount ...int) telemetry.Logger {
	container.logger.Debug("creating telemetry.Logger")
	if len(skipFrameCount) > 0 {
		return logger(skipFrameCount[0])
	}
	return logger(3)
}

// GormLogger creates a new instance of gormLogger.Interface
func (container *Container) GormLogger() gormLogger.Interface {
	container.logger.Debug("creating gormLogger.Interface")
	return telemetry.NewGormLogger(
		container.Tracer(),
		container.Logger(6),
	)
}

func (container *Container) connect(dsn string, config *gorm.Config) (db *gorm.DB, err error) {
	return gorm.Open(postgres.Open(dsn), config)
}

// DedicatedDB creates an instance of gorm.DB if it has not been created already
func (container *Container) DedicatedDB() (db *gorm.DB) {
	container.logger.Debug(fmt.Sprintf("creating %T", db))
	if container.dedicatedDB != nil {
		return container.dedicatedDB
	}

	config := &gorm.Config{
		TranslateError: true,
	}
	if isLocal() {
		config = &gorm.Config{Logger: container.GormLogger()}
	}

	db, err := container.connect(os.Getenv("DATABASE_URL_DEDICATED"), config)
	if err != nil {
		container.logger.Fatal(err)
	}

	if err = db.Use(tracing.NewPlugin()); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, "cannot use GORM tracing plugin"))
	}

	container.dedicatedDB = db
	if os.Getenv("DATABASE_MIGRATION_SKIP") != "" {
		container.logger.Debug(fmt.Sprintf("skipping migrations for [%T]", db))
		return container.dedicatedDB
	}

	container.logger.Debug(fmt.Sprintf("Running migrations for dedicated [%T]", db))
	if err = db.AutoMigrate(&entities.Heartbeat{}); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.Heartbeat{})))
	}

	if err = db.AutoMigrate(&entities.HeartbeatMonitor{}); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.HeartbeatMonitor{})))
	}

	return container.dedicatedDB
}

// MongoDB creates a *mongo.Database connection to MongoDB Atlas
func (container *Container) MongoDB() *mongoDriver.Database {
	if container.mongoDB != nil {
		return container.mongoDB
	}

	container.logger.Debug("creating MongoDB *mongo.Database connection")

	db, err := repositories.NewMongoDB(os.Getenv("MONGODB_URI"))
	if err != nil {
		container.logger.Fatal(err)
	}

	container.mongoDB = db
	return container.mongoDB
}

// DBWithoutMigration creates an instance of gorm.DB if it has not been created already
func (container *Container) DBWithoutMigration() (db *gorm.DB) {
	if container.db != nil {
		return container.db
	}

	container.logger.Debug(fmt.Sprintf("creating %T", db))

	config := &gorm.Config{
		TranslateError: true,
		Logger:         container.GormLogger(),
	}

	db, err := gorm.Open(postgres.Open(os.Getenv("DATABASE_URL")), config)
	if err != nil {
		container.logger.Fatal(err)
	}
	container.db = db

	if err = db.Use(tracing.NewPlugin()); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, "cannot use GORM tracing plugin"))
	}
	return container.db
}

// DB creates an instance of gorm.DB if it has not been created already
func (container *Container) DB() (db *gorm.DB) {
	if container.db != nil {
		return container.db
	}

	container.logger.Debug(fmt.Sprintf("creating %T", db))

	config := &gorm.Config{
		TranslateError: true,
		Logger:         container.GormLogger(),
	}

	db, err := gorm.Open(postgres.Open(os.Getenv("DATABASE_URL")), config)
	if err != nil {
		container.logger.Fatal(err)
	}
	container.db = db

	if err = db.Use(tracing.NewPlugin()); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, "cannot use GORM tracing plugin"))
	}

	if os.Getenv("DATABASE_MIGRATION_SKIP") != "" {
		container.logger.Debug(fmt.Sprintf("skipping migrations for [%T]", db))
		return container.db
	}

	container.logger.Debug(fmt.Sprintf("Running migrations for %T", db))

	// This prevents a bug in the Gorm AutoMigrate where it tries to delete this no existent constraints
	// This is only applicable to PROD on cockroachDB
	if os.Getenv("DATABASE_MIGRATION_CONSTRAINT_FIX") == "1" {
		db.Exec(`
ALTER TABLE users ADD CONSTRAINT IF NOT EXISTS uni_users_api_key CHECK (api_key IS NOT NULL);
ALTER TABLE phone_api_keys ADD CONSTRAINT IF NOT EXISTS uni_phone_api_keys_api_key CHECK (api_key IS NOT NULL);
ALTER TABLE discords ADD CONSTRAINT IF NOT EXISTS uni_discords_server_id CHECK (server_id IS NOT NULL);`)
	}

	if err = db.AutoMigrate(&entities.Message{}); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.Message{})))
	}

	if err = db.AutoMigrate(&entities.MessageThread{}); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.MessageThread{})))
	}

	if err = db.AutoMigrate(&entities.User{}); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.User{})))
	}

	if err = db.AutoMigrate(&entities.MessageSendSchedule{}); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.MessageSendSchedule{})))
	}

	if err = db.AutoMigrate(&entities.Phone{}); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.Phone{})))
	}

	if err = db.AutoMigrate(&entities.PhoneNotification{}); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.PhoneNotification{})))
	}

	if err = db.AutoMigrate(&entities.BillingUsage{}); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.BillingUsage{})))
	}

	if err = db.AutoMigrate(&entities.Webhook{}); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.Webhook{})))
	}

	if err = db.AutoMigrate(&entities.Discord{}); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.Discord{})))
	}

	if err = db.AutoMigrate(&entities.Integration3CX{}); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.Integration3CX{})))
	}

	if err = db.AutoMigrate(&entities.PhoneAPIKey{}); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.PhoneAPIKey{})))
	}

	return container.db
}

// FirebaseApp creates a new instance of firebase.App
func (container *Container) FirebaseApp() (app *firebase.App) {
	container.logger.Debug(fmt.Sprintf("creating %T", app))

	app, err := firebase.NewApp(context.Background(), nil, option.WithCredentialsJSON(container.FirebaseCredentials()))
	if err != nil {
		msg := "cannot initialize firebase application"
		container.logger.Fatal(stacktrace.Propagate(err, msg))
	}
	return app
}

// InMemoryCache returns the shared in-memory cache.Cache, creating it on the first call.
func (container *Container) InMemoryCache() cache.Cache {
	if container.inMemoryCache != nil {
		return container.inMemoryCache
	}
	container.logger.Debug("creating an in memory cache")
	c := ttlCache.New(time.Hour, time.Hour*2)
	container.inMemoryCache = cache.NewMemoryCache(container.Tracer(), c)
	return container.inMemoryCache
}

// Cache creates a new instance of cache.Cache
func (container *Container) Cache() cache.Cache {
	container.logger.Debug("creating cache.Cache")
	opt, err := redis.ParseURL(os.Getenv("REDIS_URL"))
	if err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot parse redis url [%s]", os.Getenv("REDIS_URL"))))
	}
	if strings.HasPrefix(os.Getenv("REDIS_URL"), "rediss://") {
		opt.TLSConfig = &tls.Config{
			MinVersion: tls.VersionTLS12,
		}
	}

	redisClient := redis.NewClient(opt)

	// Enable tracing instrumentation.
	if err = redisotel.InstrumentTracing(redisClient); err != nil {
		container.logger.Error(stacktrace.Propagate(err, "cannot instrument redis tracing"))
	}

	// Enable metrics instrumentation.
	if err = redisotel.InstrumentMetrics(redisClient); err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, "cannot instrument redis metrics"))
	}

	return cache.NewRedisCache(container.Tracer(), redisClient)
}

// FirebaseAuthClient creates a new instance of auth.Client
func (container *Container) FirebaseAuthClient() (client *auth.Client) {
	container.logger.Debug(fmt.Sprintf("creating %T", client))
	authClient, err := container.FirebaseApp().Auth(context.Background())
	if err != nil {
		msg := "cannot initialize firebase auth client"
		container.logger.Fatal(stacktrace.Propagate(err, msg))
	}
	return authClient
}

// CloudTasksClient creates a new instance of cloudtasks.Client
func (container *Container) CloudTasksClient() (client *cloudtasks.Client) {
	container.logger.Debug(fmt.Sprintf("creating %T", client))

	client, err := cloudtasks.NewClient(context.Background(), option.WithCredentialsJSON(container.FirebaseCredentials()))
	if err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, "cannot initialize cloud tasks client"))
	}

	return client
}

// EventsQueueConfiguration creates a new instance of services.PushQueueConfig
func (container *Container) EventsQueueConfiguration() (config services.PushQueueConfig) {
	container.logger.Debug(fmt.Sprintf("creating %T", config))

	return services.PushQueueConfig{
		UserAPIKey:       os.Getenv("EVENTS_QUEUE_USER_API_KEY"),
		Name:             os.Getenv("EVENTS_QUEUE_NAME"),
		UserID:           entities.UserID(os.Getenv("EVENTS_QUEUE_USER_ID")),
		ConsumerEndpoint: os.Getenv("EVENTS_QUEUE_ENDPOINT"),
	}
}

// EventsQueue creates a new instance of services.PushQueue
func (container *Container) EventsQueue() (queue services.PushQueue) {
	container.logger.Debug("creating events services.PushQueue")

	if os.Getenv("EVENTS_QUEUE_TYPE") == "emulator" {
		return container.EmulatorEventsQueue()
	}

	return container.CloudTaskEventsQueue()
}

// EmulatorEventsQueue creates an in process instance of events services.PushQueue
func (container *Container) EmulatorEventsQueue() (queue services.PushQueue) {
	container.logger.Debug("creating emulator events services.PushQueue")
	return services.EmulatorPushQueue(
		container.Logger(),
		container.Tracer(),
		container.HTTPClient("emulator_events_queue"),
		container.EventsQueueConfiguration(),
	)
}

// CloudTaskEventsQueue creates a Google cloud task instance of events services.PushQueue
func (container *Container) CloudTaskEventsQueue() (queue services.PushQueue) {
	container.logger.Debug("creating cloud task events services.PushQueue")
	return services.NewGooglePushQueue(
		container.Logger(),
		container.Tracer(),
		container.CloudTasksClient(),
		container.EventsQueueConfiguration(),
	)
}

// FCMClient creates the appropriate FCM client based on configuration.
// When FCM_ENDPOINT is set, it returns an EmulatorFCMClient that sends
// notifications directly to the phone emulator via HTTP.
// Otherwise, it returns a FirebaseFCMClient that uses the real Firebase SDK.
func (container *Container) FCMClient() services.FCMClient {
	if fcmEndpoint := os.Getenv("FCM_ENDPOINT"); fcmEndpoint != "" {
		container.logger.Info(fmt.Sprintf("using emulator FCM client with endpoint: %s", fcmEndpoint))
		return services.NewEmulatorFCMClient(
			container.HTTPClient("emulator_fcm"),
			fcmEndpoint,
			container.Logger(),
		)
	}

	container.logger.Debug("creating FirebaseFCMClient")
	messagingClient, err := container.FirebaseApp().Messaging(context.Background())
	if err != nil {
		msg := "cannot initialize firebase messaging client"
		container.logger.Fatal(stacktrace.Propagate(err, msg))
	}
	return services.NewFirebaseFCMClient(messagingClient)
}

// FirebaseCredentials returns firebase credentials as bytes.
func (container *Container) FirebaseCredentials() []byte {
	container.logger.Debug("creating firebase credentials")
	return []byte(os.Getenv("FIREBASE_CREDENTIALS"))
}

// Tracer creates a new instance of telemetry.Tracer
func (container *Container) Tracer() (t telemetry.Tracer) {
	container.logger.Debug("creating telemetry.Tracer")
	return telemetry.NewOtelLogger(
		container.projectID,
		container.Logger(),
	)
}

// MessageHandlerValidator creates a new instance of validators.MessageHandlerValidator
func (container *Container) MessageHandlerValidator() (validator *validators.MessageHandlerValidator) {
	container.logger.Debug(fmt.Sprintf("creating %T", validator))
	return validators.NewMessageHandlerValidator(
		container.Logger(),
		container.Tracer(),
		container.PhoneService(),
		container.TurnstileTokenValidator(),
		container.Cache(),
	)
}

// TurnstileTokenValidator creates a new instance of validators.TurnstileTokenValidator
func (container *Container) TurnstileTokenValidator() (validator *validators.TurnstileTokenValidator) {
	container.logger.Debug(fmt.Sprintf("creating %T", validator))
	return validators.NewTurnstileTokenValidator(
		container.Logger(),
		container.Tracer(),
		os.Getenv("CLOUDFLARE_TURNSTILE_SECRET_KEY"),
		container.HTTPClient("turnstile"),
	)
}

// BulkMessageHandlerValidator creates a new instance of validators.BulkMessageHandlerValidator
func (container *Container) BulkMessageHandlerValidator() (validator *validators.BulkMessageHandlerValidator) {
	container.logger.Debug(fmt.Sprintf("creating %T", validator))
	return validators.NewBulkMessageHandlerValidator(
		container.Logger(),
		container.Tracer(),
		container.PhoneService(),
		container.UserService(),
		container.Cache(),
	)
}

// HeartbeatHandler creates a new instance of handlers.HeartbeatHandler
func (container *Container) HeartbeatHandler() (h *handlers.HeartbeatHandler) {
	container.logger.Debug(fmt.Sprintf("creating %T", h))
	return handlers.NewHeartbeatHandler(
		container.Logger(),
		container.Tracer(),
		container.HeartbeatHandlerValidator(),
		container.HeartbeatService(),
	)
}

// BillingHandler creates a new instance of handlers.BillingHandler
func (container *Container) BillingHandler() (h *handlers.BillingHandler) {
	container.logger.Debug(fmt.Sprintf("creating %T", h))
	return handlers.NewBillingHandler(
		container.Logger(),
		container.Tracer(),
		container.BillingHandlerValidator(),
		container.BillingService(),
	)
}

// WebhookHandler creates a new instance of handlers.WebhookHandler
func (container *Container) WebhookHandler() (h *handlers.WebhookHandler) {
	container.logger.Debug(fmt.Sprintf("creating %T", h))
	return handlers.NewWebhookHandler(
		container.Logger(),
		container.Tracer(),
		container.WebhookService(),
		container.WebhookHandlerValidator(),
	)
}

// HeartbeatHandlerValidator creates a new instance of validators.HeartbeatHandlerValidator
func (container *Container) HeartbeatHandlerValidator() (validator *validators.HeartbeatHandlerValidator) {
	container.logger.Debug(fmt.Sprintf("creating %T", validator))
	return validators.NewHeartbeatHandlerValidator(
		container.Logger(),
		container.Tracer(),
	)
}

// BillingHandlerValidator creates a new instance of validators.BillingHandlerValidator
func (container *Container) BillingHandlerValidator() (validator *validators.BillingHandlerValidator) {
	container.logger.Debug(fmt.Sprintf("creating %T", validator))
	return validators.NewBillingHandlerValidator(
		container.Logger(),
		container.Tracer(),
	)
}

// DiscordHandlerValidator creates a new instance of validators.DiscordHandlerValidator
func (container *Container) DiscordHandlerValidator() (validator *validators.DiscordHandlerValidator) {
	container.logger.Debug(fmt.Sprintf("creating %T", validator))
	return validators.NewDiscordHandlerValidator(
		container.Logger(),
		container.Tracer(),
		container.DiscordClient(),
	)
}

// WebhookHandlerValidator creates a new instance of validators.WebhookHandlerValidator
func (container *Container) WebhookHandlerValidator() (validator *validators.WebhookHandlerValidator) {
	container.logger.Debug(fmt.Sprintf("creating %T", validator))
	return validators.NewWebhookHandlerValidator(
		container.Logger(),
		container.Tracer(),
		container.PhoneService(),
	)
}

// MessageThreadHandler creates a new instance of handlers.MessageThreadHandler
func (container *Container) MessageThreadHandler() (h *handlers.MessageThreadHandler) {
	container.logger.Debug(fmt.Sprintf("creating %T", h))
	return handlers.NewMessageThreadHandler(
		container.Logger(),
		container.Tracer(),
		container.MessageThreadHandlerValidator(),
		container.MessageThreadService(),
	)
}

// MessageThreadHandlerValidator creates a new instance of validators.MessageThreadHandlerValidator
func (container *Container) MessageThreadHandlerValidator() (validator *validators.MessageThreadHandlerValidator) {
	container.logger.Debug(fmt.Sprintf("creating %T", validator))
	return validators.NewMessageThreadHandlerValidator(
		container.Logger(),
		container.Tracer(),
	)
}

// PhoneHandlerValidator creates a new instance of validators.PhoneHandlerValidator
func (container *Container) PhoneHandlerValidator() (validator *validators.PhoneHandlerValidator) {
	container.logger.Debug(fmt.Sprintf("creating %T", validator))
	return validators.NewPhoneHandlerValidator(
		container.Logger(),
		container.Tracer(),
		container.MessageSendScheduleService(),
	)
}

// UserHandlerValidator creates a new instance of validators.UserHandlerValidator
func (container *Container) UserHandlerValidator() (validator *validators.UserHandlerValidator) {
	container.logger.Debug(fmt.Sprintf("creating %T", validator))
	return validators.NewUserHandlerValidator(
		container.Logger(),
		container.Tracer(),
		container.UserService(),
	)
}

// EventDispatcher creates a new instance of services.EventDispatcher
func (container *Container) EventDispatcher() (dispatcher *services.EventDispatcher) {
	if container.eventDispatcher != nil {
		return container.eventDispatcher
	}

	container.logger.Debug(fmt.Sprintf("creating %T", dispatcher))
	dispatcher = services.NewEventDispatcher(
		container.Logger(),
		container.Tracer(),
		container.Float64Histogram("event.publisher.duration", "ms", "measures the duration of processing CloudEvents"),
		container.EventsQueue(),
		container.EventsQueueConfiguration(),
	)

	container.eventDispatcher = dispatcher
	return dispatcher
}

// Float64Histogram creates a new instance of metric.Float64Histogram
func (container *Container) Float64Histogram(name, unit, description string) otelMetric.Float64Histogram {
	container.logger.Debug("creating GORM repositories.MessageRepository")
	meter := otel.GetMeterProvider().Meter(
		container.projectID,
		otelMetric.WithInstrumentationVersion(otel.Version()),
	)
	histogram, err := meter.Float64Histogram(name, otelMetric.WithUnit(unit), otelMetric.WithDescription(description))
	if err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, "cannot create float64 histogram"))
	}
	return histogram
}

// MessageRepository creates a new instance of repositories.MessageRepository
func (container *Container) MessageRepository() (repository repositories.MessageRepository) {
	container.logger.Debug("creating GORM repositories.MessageRepository")
	return repositories.NewGormMessageRepository(
		container.Logger(),
		container.Tracer(),
		container.DB(),
	)
}

// PhoneAPIKeyRepository creates a new instance of repositories.PhoneAPIKeyRepository
func (container *Container) PhoneAPIKeyRepository() (repository repositories.PhoneAPIKeyRepository) {
	container.logger.Debug("creating GORM repositories.PhoneAPIKeyRepository")
	return repositories.NewGormPhoneAPIKeyRepository(
		container.Logger(),
		container.Tracer(),
		container.DB(),
		container.UserRistrettoCache(),
	)
}

// Integration3CXRepository creates a new instance of repositories.Integration3CxRepository
func (container *Container) Integration3CXRepository() (repository repositories.Integration3CxRepository) {
	container.logger.Debug("creating GORM repositories.Integration3CxRepository")
	return repositories.NewGormIntegration3CXRepository(
		container.Logger(),
		container.Tracer(),
		container.DB(),
	)
}

// PhoneRepository creates a new instance of repositories.PhoneRepository
func (container *Container) PhoneRepository() (repository repositories.PhoneRepository) {
	container.logger.Debug("creating GORM repositories.PhoneRepository")
	return repositories.NewGormPhoneRepository(
		container.Logger(),
		container.Tracer(),
		container.DB(),
		container.PhoneRistrettoCache(),
	)
}

// MessageSendScheduleRepository creates a new instance of repositories.MessageSendScheduleRepository
func (container *Container) MessageSendScheduleRepository() repositories.MessageSendScheduleRepository {
	container.logger.Debug("creating GORM repositories.MessageSendScheduleRepository")
	return repositories.NewGormMessageSendScheduleRepository(
		container.Logger(),
		container.Tracer(),
		container.DB(),
	)
}

// MessageSendScheduleService creates a new instance of services.MessageSendScheduleService
func (container *Container) MessageSendScheduleService() *services.MessageSendScheduleService {
	container.logger.Debug("creating services.MessageSendScheduleService")
	return services.NewMessageSendScheduleService(
		container.Logger(),
		container.Tracer(),
		container.MessageSendScheduleRepository(),
		container.EventDispatcher(),
	)
}

// MessageSendScheduleHandlerValidator creates a new instance of validators.MessageSendScheduleHandlerValidator
func (container *Container) MessageSendScheduleHandlerValidator() *validators.MessageSendScheduleHandlerValidator {
	container.logger.Debug("creating validators.MessageSendScheduleHandlerValidator")
	return validators.NewMessageSendScheduleHandlerValidator(
		container.Logger(),
		container.Tracer(),
	)
}

// MessageSendScheduleHandler creates a new instance of handlers.MessageSendScheduleHandler
func (container *Container) MessageSendScheduleHandler() *handlers.MessageSendScheduleHandler {
	container.logger.Debug("creating handlers.MessageSendScheduleHandler")
	return handlers.NewMessageSendScheduleHandler(
		container.Logger(),
		container.Tracer(),
		container.MessageSendScheduleHandlerValidator(),
		container.MessageSendScheduleService(),
		container.EntitlementService(),
	)
}

// BillingUsageRepository creates a new instance of repositories.BillingUsageRepository
func (container *Container) BillingUsageRepository() (repository repositories.BillingUsageRepository) {
	container.logger.Debug("creating GORM repositories.BillingUsageRepository")
	return repositories.NewGormBillingUsageRepository(
		container.Logger(),
		container.Tracer(),
		container.DB(),
	)
}

// EntitlementService creates a new instance of services.EntitlementService
func (container *Container) EntitlementService() *services.EntitlementService {
	container.logger.Debug("creating services.EntitlementService")
	return services.NewEntitlementService(
		container.Logger(),
		container.Tracer(),
		os.Getenv("ENTITLEMENT_ENABLED") == "true",
		container.UserRepository(),
	)
}

// DiscordRepository creates a new instance of repositories.DiscordRepository
func (container *Container) DiscordRepository() (repository repositories.DiscordRepository) {
	container.logger.Debug("creating GORM repositories.DiscordRepository")
	return repositories.NewGormDiscordRepository(
		container.Logger(),
		container.Tracer(),
		container.DB(),
	)
}

// WebhookRepository creates a new instance of repositories.WebhookRepository
func (container *Container) WebhookRepository() (repository repositories.WebhookRepository) {
	container.logger.Debug("creating GORM repositories.WebhookRepository")
	return repositories.NewGormWebhookRepository(
		container.Logger(),
		container.Tracer(),
		container.DB(),
	)
}

// PhoneNotificationRepository creates a new instance of repositories.PhoneNotificationRepository
func (container *Container) PhoneNotificationRepository() (repository repositories.PhoneNotificationRepository) {
	container.logger.Debug("creating GORM repositories.PhoneNotificationRepository")
	return repositories.NewGormPhoneNotificationRepository(
		container.Logger(),
		container.Tracer(),
		container.DB(),
	)
}

// MessageThreadRepository creates a new instance of repositories.MessageThreadRepository
func (container *Container) MessageThreadRepository() (repository repositories.MessageThreadRepository) {
	container.logger.Debug("creating GORM repositories.MessageThreadRepository")
	return repositories.NewGormMessageThreadRepository(
		container.Logger(),
		container.Tracer(),
		container.DB(),
	)
}

// HeartbeatMonitorRepository creates a new instance of repositories.HeartbeatMonitorRepository
func (container *Container) HeartbeatMonitorRepository() (repository repositories.HeartbeatMonitorRepository) {
	switch os.Getenv("HEARTBEAT_DB_BACKEND") {
	case "mongodb":
		container.logger.Debug("creating MongoDB repositories.HeartbeatMonitorRepository")
		return repositories.NewMongoHeartbeatMonitorRepository(
			container.Logger(),
			container.Tracer(),
			container.MongoDB(),
		)
	default:
		container.logger.Debug("creating GORM repositories.HeartbeatMonitorRepository")
		return repositories.NewGormHeartbeatMonitorRepository(
			container.Logger(),
			container.Tracer(),
			container.DedicatedDB(),
		)
	}
}

// HeartbeatService creates a new instance of services.HeartbeatService
func (container *Container) HeartbeatService() (service *services.HeartbeatService) {
	container.logger.Debug(fmt.Sprintf("creating %T", service))
	return services.NewHeartbeatService(
		container.Logger(),
		container.Tracer(),
		container.HeartbeatRepository(),
		container.HeartbeatMonitorRepository(),
		container.EventDispatcher(),
	)
}

// BillingService creates a new instance of services.BillingService
func (container *Container) BillingService() (service *services.BillingService) {
	container.logger.Debug(fmt.Sprintf("creating %T", service))
	return services.NewBillingService(
		container.Logger(),
		container.Tracer(),
		container.InMemoryCache(),
		container.Mailer(),
		container.UserEmailFactory(),
		container.BillingUsageRepository(),
		container.UserRepository(),
	)
}

// DiscordService creates a new instance of services.DiscordService
func (container *Container) DiscordService() (service *services.DiscordService) {
	container.logger.Debug(fmt.Sprintf("creating %T", service))
	return services.NewDiscordService(
		container.Logger(),
		container.Tracer(),
		container.DiscordClient(),
		container.DiscordRepository(),
		container.EventDispatcher(),
	)
}

// WebhookService creates a new instance of services.WebhookService
func (container *Container) WebhookService() (service *services.WebhookService) {
	container.logger.Debug(fmt.Sprintf("creating %T", service))
	return services.NewWebhookService(
		container.Logger(),
		container.Tracer(),
		&http.Client{
			Timeout:   6 * time.Second,
			Transport: container.HTTPRoundTripperWithoutRetry("webhook"),
		},
		container.WebhookRepository(),
		container.EventDispatcher(),
	)
}

// Integration3CXService creates a new instance of services.Integration3CXService
func (container *Container) Integration3CXService() (service *services.Integration3CXService) {
	container.logger.Debug(fmt.Sprintf("creating %T", service))
	return services.NewIntegration3CXService(
		container.Logger(),
		container.Tracer(),
		container.HTTPClient("integration_3cx"),
		container.Integration3CXRepository(),
	)
}

// HTTPClient creates a new http.Client
func (container *Container) HTTPClient(name string) *http.Client {
	container.logger.Debug(fmt.Sprintf("creating %s %T", name, http.DefaultClient))
	return &http.Client{
		Timeout:   60 * time.Second,
		Transport: container.HTTPRoundTripper(name),
	}
}

// HTTPRoundTripper creates an open telemetry http.RoundTripper
func (container *Container) HTTPRoundTripper(name string) http.RoundTripper {
	container.logger.Debug(fmt.Sprintf("Debug: initializing %s %T", name, http.DefaultTransport))
	return otelroundtripper.New(
		otelroundtripper.WithName(name),
		otelroundtripper.WithParent(container.RetryHTTPRoundTripper()),
		otelroundtripper.WithMeter(otel.GetMeterProvider().Meter(container.projectID)),
	)
}

// HTTPRoundTripperWithoutRetry creates an open telemetry http.RoundTripper without retry
func (container *Container) HTTPRoundTripperWithoutRetry(name string) http.RoundTripper {
	container.logger.Debug(fmt.Sprintf("Debug: initializing %s %T", name, http.DefaultTransport))
	return otelroundtripper.New(
		otelroundtripper.WithName(name),
		otelroundtripper.WithMeter(otel.GetMeterProvider().Meter(container.projectID)),
	)
}

// OtelResources generates default open telemetry resources
func (container *Container) OtelResources(version string, namespace string) *resource.Resource {
	return resource.NewWithAttributes(
		semconv.SchemaURL,
		semconv.ServiceNameKey.String(namespace),
		semconv.ServiceVersionKey.String(version),
		semconv.ServiceInstanceIDKey.String(instanceID()),
		semconv.DeploymentEnvironmentKey.String(os.Getenv("ENV")),
	)
}

// RetryHTTPRoundTripper creates a retryable http.RoundTripper
func (container *Container) RetryHTTPRoundTripper() http.RoundTripper {
	container.logger.Debug(fmt.Sprintf("initializing retry %T", http.DefaultTransport))
	retryClient := retryablehttp.NewClient()
	retryClient.Logger = container.Logger()
	retryClient.RetryMax = 2
	return retryClient.StandardClient().Transport
}

// PhoneService creates a new instance of services.PhoneService
func (container *Container) PhoneService() (service *services.PhoneService) {
	container.logger.Debug(fmt.Sprintf("creating %T", service))
	return services.NewPhoneService(
		container.Logger(),
		container.Tracer(),
		container.PhoneRepository(),
		container.EventDispatcher(),
	)
}

// MarketingService creates a new instance of services.MarketingService
func (container *Container) MarketingService() (service *services.MarketingService) {
	container.logger.Debug(fmt.Sprintf("creating %T", service))
	return services.NewMarketingService(
		container.Logger(),
		container.Tracer(),
		container.FirebaseAuthClient(),
		container.PlunkClient(),
	)
}

// UserService creates a new instance of services.UserService
func (container *Container) UserService() (service *services.UserService) {
	container.logger.Debug(fmt.Sprintf("creating %T", service))
	return services.NewUserService(
		container.Logger(),
		container.Tracer(),
		container.UserRepository(),
		container.Mailer(),
		container.UserEmailFactory(),
		container.LemonsqueezyClient(),
		container.EventDispatcher(),
		container.FirebaseAuthClient(),
		container.HTTPClient("lemonsqueezy"),
	)
}

// Mailer creates a new instance of emails.Mailer
func (container *Container) Mailer() (mailer emails.Mailer) {
	container.logger.Debug("creating emails.Mailer")
	return emails.NewSMTPEmailService(
		container.Tracer(),
		emails.SMTPConfig{
			FromName:  os.Getenv("SMTP_FROM_NAME"),
			FromEmail: os.Getenv("SMTP_FROM_EMAIL"),
			Username:  os.Getenv("SMTP_USERNAME"),
			Password:  os.Getenv("SMTP_PASSWORD"),
			Hostname:  os.Getenv("SMTP_HOST"),
			Port:      os.Getenv("SMTP_PORT"),
		},
	)
}

// UserEmailFactory creates a new instance of emails.UserEmailFactory
func (container *Container) UserEmailFactory() (factory emails.UserEmailFactory) {
	container.logger.Debug("creating emails.UserEmailFactory")
	return emails.NewHermesUserEmailFactory(&emails.HermesGeneratorConfig{
		AppURL:     os.Getenv("APP_URL"),
		AppName:    os.Getenv("APP_NAME"),
		AppLogoURL: os.Getenv("APP_LOGO_URL"),
	})
}

// NotificationEmailFactory creates a new instance of emails.NotificationEmailFactory
func (container *Container) NotificationEmailFactory() (factory emails.NotificationEmailFactory) {
	container.logger.Debug("creating emails.UserEmailFactory")
	return emails.NewHermesNotificationEmailFactory(&emails.HermesGeneratorConfig{
		AppURL:     os.Getenv("APP_URL"),
		AppName:    os.Getenv("APP_NAME"),
		AppLogoURL: os.Getenv("APP_LOGO_URL"),
	})
}

// MessageThreadService creates a new instance of services.MessageService
func (container *Container) MessageThreadService() (service *services.MessageThreadService) {
	container.logger.Debug(fmt.Sprintf("creating %T", service))
	return services.NewMessageThreadService(
		container.Logger(),
		container.Tracer(),
		container.MessageThreadRepository(),
		container.EventDispatcher(),
	)
}

// EmailNotificationService creates a new instance of services.EmailNotificationService
func (container *Container) EmailNotificationService() (service *services.EmailNotificationService) {
	container.logger.Debug(fmt.Sprintf("creating %T", service))
	return services.NewEmailNotificationService(
		container.Logger(),
		container.Tracer(),
		container.UserRepository(),
		container.NotificationEmailFactory(),
		container.Mailer(),
		container.Cache(),
	)
}

// MessageHandler creates a new instance of handlers.MessageHandler
func (container *Container) MessageHandler() (handler *handlers.MessageHandler) {
	container.logger.Debug(fmt.Sprintf("creating %T", handler))
	return handlers.NewMessageHandler(
		container.Logger(),
		container.Tracer(),
		container.MessageHandlerValidator(),
		container.BillingService(),
		container.MessageService(),
	)
}

// BulkMessageHandler creates a new instance of handlers.BulkMessageHandler
func (container *Container) BulkMessageHandler() (handler *handlers.BulkMessageHandler) {
	container.logger.Debug(fmt.Sprintf("creating %T", handler))
	return handlers.NewBulkMessageHandler(
		container.Logger(),
		container.Tracer(),
		container.BulkMessageHandlerValidator(),
		container.BillingService(),
		container.MessageService(),
	)
}

// UserHandler creates a new instance of handlers.MessageHandler
func (container *Container) UserHandler() (handler *handlers.UserHandler) {
	container.logger.Debug(fmt.Sprintf("creating %T", handler))
	return handlers.NewUserHandler(
		container.Logger(),
		container.Tracer(),
		container.UserHandlerValidator(),
		container.UserService(),
	)
}

// PhoneHandler creates a new instance of handlers.PhoneHandler
func (container *Container) PhoneHandler() (handler *handlers.PhoneHandler) {
	container.logger.Debug(fmt.Sprintf("creating %T", handler))
	return handlers.NewPhoneHandler(
		container.Logger(),
		container.Tracer(),
		container.PhoneService(),
		container.PhoneHandlerValidator(),
	)
}

// EventsHandler creates a new instance of handlers.EventsHandler
func (container *Container) EventsHandler() (handler *handlers.EventsHandler) {
	container.logger.Debug(fmt.Sprintf("creating %T", handler))

	return handlers.NewEventsHandler(
		container.Logger(),
		container.Tracer(),
		container.EventsQueueConfiguration(),
		container.EventDispatcher(),
	)
}

// RegisterMessageListeners registers event listeners for listeners.MessageListener
func (container *Container) RegisterMessageListeners() {
	container.logger.Debug(fmt.Sprintf("registering listners for %T", listeners.MessageListener{}))
	_, routes := listeners.NewMessageListener(
		container.Logger(),
		container.Tracer(),
		container.MessageService(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// RegisterMessageSendScheduleListeners registers event listeners for listeners.MessageSendScheduleListener
func (container *Container) RegisterMessageSendScheduleListeners() {
	container.logger.Debug(fmt.Sprintf("registering listeners for %T", listeners.MessageSendScheduleListener{}))
	_, routes := listeners.NewMessageSendScheduleListener(
		container.Logger(),
		container.Tracer(),
		container.MessageSendScheduleService(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// LemonsqueezyService creates a new instance of services.LemonsqueezyService
func (container *Container) LemonsqueezyService() (service *services.LemonsqueezyService) {
	container.logger.Debug(fmt.Sprintf("creating %T", service))
	return services.NewLemonsqueezyService(
		container.Logger(),
		container.Tracer(),
		container.UserRepository(),
		container.EventDispatcher(),
	)
}

// LemonsqueezyHandler creates a new instance of handlers.LemonsqueezyHandler
func (container *Container) LemonsqueezyHandler() (handler *handlers.LemonsqueezyHandler) {
	container.logger.Debug(fmt.Sprintf("creating %T", handler))

	return handlers.NewLemonsqueezyHandler(
		container.Logger(),
		container.Tracer(),
		container.LemonsqueezyService(),
		container.LemonsqueezyHandlerValidator(),
	)
}

// Integration3CXHandler creates a new instance of handlers.Integration3CXHandler
func (container *Container) Integration3CXHandler() (handler *handlers.Integration3CXHandler) {
	container.logger.Debug(fmt.Sprintf("creating %T", handler))

	return handlers.NewIntegration3CxHandler(
		container.Logger(),
		container.Tracer(),
		container.MessageService(),
		container.BillingService(),
	)
}

// PhoneAPIKeyHandler creates a new instance of handlers.PhoneAPIKeyHandler
func (container *Container) PhoneAPIKeyHandler() (handler *handlers.PhoneAPIKeyHandler) {
	container.logger.Debug(fmt.Sprintf("creating %T", handler))

	return handlers.NewPhoneAPIKeyHandler(
		container.Logger(),
		container.Tracer(),
		container.PhoneAPIKeyHandlerValidator(),
		container.PhoneAPIKeyService(),
		container.EntitlementService(),
	)
}

// DiscordHandler creates a new instance of handlers.DiscordHandler
func (container *Container) DiscordHandler() (handler *handlers.DiscordHandler) {
	container.logger.Debug(fmt.Sprintf("creating %T", handler))

	return handlers.NewDiscordHandler(
		container.Logger(),
		container.Tracer(),
		container.DiscordHandlerValidator(),
		container.DiscordService(),
		container.MessageService(),
		container.BillingService(),
		container.MessageHandlerValidator(),
	)
}

// LemonsqueezyHandlerValidator creates a new instance of validators.LemonsqueezyHandlerValidator
func (container *Container) LemonsqueezyHandlerValidator() (validator *validators.LemonsqueezyHandlerValidator) {
	container.logger.Debug(fmt.Sprintf("creating %T", validator))
	return validators.NewLemonsqueezyHandlerValidator(
		container.Logger(),
		container.Tracer(),
		container.LemonsqueezyClient(),
	)
}

// PhoneAPIKeyHandlerValidator creates a new instance of validators.PhoneAPIKeyHandlerValidator
func (container *Container) PhoneAPIKeyHandlerValidator() (validator *validators.PhoneAPIKeyHandlerValidator) {
	container.logger.Debug(fmt.Sprintf("creating %T", validator))
	return validators.NewPhoneAPIKeyHandlerValidator(
		container.Logger(),
		container.Tracer(),
	)
}

// LemonsqueezyClient creates a new instance of lemonsqueezy.Client
func (container *Container) LemonsqueezyClient() (client *lemonsqueezy.Client) {
	container.logger.Debug(fmt.Sprintf("creating %T", client))
	return lemonsqueezy.New(
		lemonsqueezy.WithHTTPClient(container.HTTPClient("lemonsqueezy")),
		lemonsqueezy.WithAPIKey(os.Getenv("LEMONSQUEEZY_API_KEY")),
		lemonsqueezy.WithSigningSecret(os.Getenv("LEMONSQUEEZY_SIGNING_SECRET")),
	)
}

// PusherClient creates a new instance of pusher.Client
func (container *Container) PusherClient() (client *pusher.Client) {
	container.logger.Debug(fmt.Sprintf("creating %T", client))
	return &pusher.Client{
		AppID:   os.Getenv("PUSHER_APP_ID"),
		Key:     os.Getenv("PUSHER_KEY"),
		Secret:  os.Getenv("PUSHER_SECRET"),
		Cluster: os.Getenv("PUSHER_CLUSTER"),
		Secure:  true,
	}
}

// DiscordClient creates a new instance of discord.Client
func (container *Container) DiscordClient() (client *discord.Client) {
	container.logger.Debug(fmt.Sprintf("creating %T", client))
	return discord.New(
		discord.WithHTTPClient(container.HTTPClient("discord")),
		discord.WithApplicationID(os.Getenv("DISCORD_APPLICATION_ID")),
		discord.WithBotToken(os.Getenv("DISCORD_BOT_TOKEN")),
	)
}

// PlunkClient creates a new instance of plunk.Client
func (container *Container) PlunkClient() (client *plunk.Client) {
	container.logger.Debug(fmt.Sprintf("creating %T", client))
	return plunk.New(
		plunk.WithHTTPClient(container.HTTPClient("plunk")),
		plunk.WithSecretKey(os.Getenv("PLUNK_SECRET_KEY")),
		plunk.WithPublicKey(os.Getenv("PLUNK_PUBLIC_KEY")),
	)
}

// RegisterLemonsqueezyRoutes registers routes for the /lemonsqueezy prefix
func (container *Container) RegisterLemonsqueezyRoutes() {
	container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.LemonsqueezyHandler{}))
	container.LemonsqueezyHandler().RegisterRoutes(container.App())
}

// RegisterIntegration3CXRoutes registers routes for the /integration/3cx prefix
func (container *Container) RegisterIntegration3CXRoutes() {
	container.logger.Debug(fmt.Sprintf("registering [%T] routes", &handlers.Integration3CXHandler{}))
	container.Integration3CXHandler().RegisterRoutes(container.App(), container.BearerAPIKeyMiddleware(), container.AuthenticatedMiddleware())
}

// RegisterPhoneAPIKeyRoutes registers routes for the /phone-api-key prefix
func (container *Container) RegisterPhoneAPIKeyRoutes() {
	container.logger.Debug(fmt.Sprintf("registering [%T] routes", &handlers.Integration3CXHandler{}))
	container.PhoneAPIKeyHandler().RegisterRoutes(container.App(), container.AuthenticatedMiddleware())
}

// RegisterDiscordRoutes registers routes for the /discord prefix
func (container *Container) RegisterDiscordRoutes() {
	container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.DiscordHandler{}))
	container.DiscordHandler().RegisterRoutes(container.App(), container.AuthenticatedMiddleware())
}

// RegisterMessageThreadListeners registers event listeners for listeners.MessageThreadListener
func (container *Container) RegisterMessageThreadListeners() {
	container.logger.Debug(fmt.Sprintf("registering listners for %T", listeners.MessageThreadListener{}))
	_, routes := listeners.NewMessageThreadListener(
		container.Logger(),
		container.Tracer(),
		container.MessageThreadService(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// RegisterEmailNotificationListeners registers event listeners for listeners.EmailNotificationListener
func (container *Container) RegisterEmailNotificationListeners() {
	container.logger.Debug(fmt.Sprintf("registering listners for %T", listeners.EmailNotificationListener{}))
	_, routes := listeners.NewEmailNotificationListener(
		container.Logger(),
		container.Tracer(),
		container.EmailNotificationService(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// RegisterNotificationListeners registers event listeners for listeners.PhoneNotificationListener
func (container *Container) RegisterNotificationListeners() {
	container.logger.Debug(fmt.Sprintf("registering listners for %T", listeners.PhoneNotificationListener{}))
	_, routes := listeners.NewNotificationListener(
		container.Logger(),
		container.Tracer(),
		container.NotificationService(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// RegisterHeartbeatListeners registers event listeners for listeners.HeartbeatListener
func (container *Container) RegisterHeartbeatListeners() {
	container.logger.Debug(fmt.Sprintf("registering listners for %T", listeners.HeartbeatListener{}))
	_, routes := listeners.NewHeartbeatListener(
		container.Logger(),
		container.Tracer(),
		container.HeartbeatService(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// RegisterUserListeners registers event listeners for listeners.UserListener
func (container *Container) RegisterUserListeners() {
	container.logger.Debug(fmt.Sprintf("registering listners for %T", listeners.UserListener{}))
	_, routes := listeners.NewUserListener(
		container.Logger(),
		container.Tracer(),
		container.UserService(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// RegisterBillingListeners registers event listeners for listeners.BillingListener
func (container *Container) RegisterBillingListeners() {
	container.logger.Debug(fmt.Sprintf("registering listeners for %T", listeners.BillingListener{}))
	_, routes := listeners.NewBillingListener(
		container.Logger(),
		container.Tracer(),
		container.BillingService(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// RegisterDiscordListeners registers event listeners for listeners.DiscordListener
func (container *Container) RegisterDiscordListeners() {
	container.logger.Debug(fmt.Sprintf("registering listeners for %T", listeners.DiscordListener{}))
	_, routes := listeners.NewDiscordListener(
		container.Logger(),
		container.Tracer(),
		container.DiscordService(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// RegisterMarketingListeners registers event listeners for listeners.MarketingListener
func (container *Container) RegisterMarketingListeners() {
	container.logger.Debug(fmt.Sprintf("registering listeners for %T", listeners.MarketingListener{}))

	if os.Getenv("PLUNK_SECRET_KEY") == "" {
		container.logger.Debug("skipping marketing listeners because the PLUNK_SECRET_KEY env variable is not set")
		return
	}

	_, routes := listeners.NewMarketingListener(
		container.Logger(),
		container.Tracer(),
		container.MarketingService(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// RegisterIntegration3CXListeners registers event listeners for listeners.Integration3CXListener
func (container *Container) RegisterIntegration3CXListeners() {
	container.logger.Debug(fmt.Sprintf("registering listeners for %T", listeners.Integration3CXListener{}))
	_, routes := listeners.NewIntegration3CXListener(
		container.Logger(),
		container.Tracer(),
		container.Integration3CXService(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// RegisterPhoneAPIKeyListeners registers event listeners for listeners.PhoneAPIKeyListener
func (container *Container) RegisterPhoneAPIKeyListeners() {
	container.logger.Debug(fmt.Sprintf("registering listeners for %T", listeners.PhoneAPIKeyListener{}))
	_, routes := listeners.NewPhoneAPIKeyListener(
		container.Logger(),
		container.Tracer(),
		container.PhoneAPIKeyService(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// RegisterPhoneListeners registers event listeners for listeners.PhoneListener
func (container *Container) RegisterPhoneListeners() {
	container.logger.Debug(fmt.Sprintf("registering listeners for %T", listeners.PhoneListener{}))
	_, routes := listeners.NewPhoneListener(
		container.Logger(),
		container.Tracer(),
		container.PhoneService(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// RegisterWebsocketListeners registers event listeners for listeners.WebsocketListener
func (container *Container) RegisterWebsocketListeners() {
	container.logger.Debug(fmt.Sprintf("registering listeners for %T", listeners.WebsocketListener{}))

	if os.Getenv("PUSHER_SECRET") == "" {
		container.logger.Warn(stacktrace.NewError("skipping websocket listeners because the PUSHER_SECRET env variable is not set"))
		return
	}

	_, routes := listeners.NewWebsocketListener(
		container.Logger(),
		container.Tracer(),
		container.PusherClient(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// RegisterWebhookListeners registers event listeners for listeners.WebhookListener
func (container *Container) RegisterWebhookListeners() {
	container.logger.Debug(fmt.Sprintf("registering listeners for %T", listeners.WebhookListener{}))
	_, routes := listeners.NewWebhookListener(
		container.Logger(),
		container.Tracer(),
		container.WebhookService(),
	)

	for event, handler := range routes {
		container.EventDispatcher().Subscribe(event, handler)
	}
}

// MessageService creates a new instance of services.MessageService
func (container *Container) MessageService() (service *services.MessageService) {
	container.logger.Debug(fmt.Sprintf("creating %T", service))
	return services.NewMessageService(
		container.Logger(),
		container.Tracer(),
		container.MessageRepository(),
		container.EventDispatcher(),
		container.PhoneService(),
		container.AttachmentRepository(),
		container.APIBaseURL(),
	)
}

// AttachmentRepository creates a cached AttachmentRepository based on configuration
func (container *Container) AttachmentRepository() repositories.AttachmentRepository {
	if container.attachmentRepository != nil {
		return container.attachmentRepository
	}

	bucket := os.Getenv("GCS_BUCKET_NAME")
	if bucket != "" {
		container.logger.Debug("creating GoogleCloudStorageAttachmentRepository")
		client, err := storage.NewClient(context.Background(), option.WithAuthCredentialsJSON(option.ServiceAccount, container.FirebaseCredentials()))
		if err != nil {
			container.logger.Fatal(stacktrace.Propagate(err, "cannot create GCS client"))
		}
		container.attachmentRepository = repositories.NewGoogleCloudStorageAttachmentRepository(
			container.Logger(),
			container.Tracer(),
			client,
			bucket,
		)
	} else {
		container.logger.Debug("creating MemoryAttachmentRepository (GCS_BUCKET_NAME not set)")
		container.attachmentRepository = repositories.NewMemoryAttachmentRepository(
			container.Logger(),
			container.Tracer(),
		)
	}

	return container.attachmentRepository
}

// APIBaseURL returns the API base URL derived from EVENTS_QUEUE_ENDPOINT
func (container *Container) APIBaseURL() string {
	endpoint := os.Getenv("EVENTS_QUEUE_ENDPOINT")
	return strings.TrimSuffix(endpoint, "/v1/events")
}

// AttachmentHandler creates a new AttachmentHandler
func (container *Container) AttachmentHandler() (handler *handlers.AttachmentHandler) {
	container.logger.Debug(fmt.Sprintf("creating %T", handler))
	return handlers.NewAttachmentHandler(
		container.Logger(),
		container.Tracer(),
		container.AttachmentRepository(),
	)
}

// RegisterAttachmentRoutes registers routes for the /attachments prefix
func (container *Container) RegisterAttachmentRoutes() {
	container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.AttachmentHandler{}))
	container.AttachmentHandler().RegisterRoutes(container.App())
}

// PhoneAPIKeyService creates a new instance of services.PhoneAPIKeyService
func (container *Container) PhoneAPIKeyService() (service *services.PhoneAPIKeyService) {
	container.logger.Debug(fmt.Sprintf("creating %T", service))
	return services.NewPhoneAPIKeyService(
		container.Logger(),
		container.Tracer(),
		container.PhoneRepository(),
		container.PhoneAPIKeyRepository(),
	)
}

// NotificationService creates a new instance of services.PhoneNotificationService
func (container *Container) NotificationService() (service *services.PhoneNotificationService) {
	container.logger.Debug(fmt.Sprintf("creating %T", service))
	return services.NewNotificationService(
		container.Logger(),
		container.Tracer(),
		container.FCMClient(),
		container.PhoneRepository(),
		container.PhoneNotificationRepository(),
		container.MessageSendScheduleRepository(),
		container.EventDispatcher(),
	)
}

// RegisterMessageRoutes registers routes for the /messages prefix
func (container *Container) RegisterMessageRoutes() {
	container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.MessageHandler{}))
	container.MessageHandler().RegisterPhoneAPIKeyRoutes(container.App(), container.PhoneAPIKeyMiddleware(), container.AuthenticatedMiddleware())
	container.MessageHandler().RegisterRoutes(container.App(), container.AuthenticatedMiddleware())
}

// RegisterBulkMessageRoutes registers routes for the /bulk-messages prefix
func (container *Container) RegisterBulkMessageRoutes() {
	container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.BulkMessageHandler{}))
	container.BulkMessageHandler().RegisterRoutes(container.App(), container.AuthenticatedMiddleware())
}

// RegisterMessageThreadRoutes registers routes for the /message-threads prefix
func (container *Container) RegisterMessageThreadRoutes() {
	container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.MessageThreadHandler{}))
	container.MessageThreadHandler().RegisterRoutes(container.App(), container.AuthenticatedMiddleware())
}

// RegisterHeartbeatRoutes registers routes for the /heartbeats prefix
func (container *Container) RegisterHeartbeatRoutes() {
	container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.HeartbeatHandler{}))
	container.HeartbeatHandler().RegisterRoutes(container.App(), container.AuthenticatedMiddleware())
	container.HeartbeatHandler().RegisterPhoneAPIKeyRoutes(container.App(), container.PhoneAPIKeyMiddleware(), container.AuthenticatedMiddleware())
}

// RegisterBillingRoutes registers routes for the /billing prefix
func (container *Container) RegisterBillingRoutes() {
	container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.BillingHandler{}))
	container.BillingHandler().RegisterRoutes(container.App(), container.AuthenticatedMiddleware())
}

// RegisterWebhookRoutes registers routes for the /webhooks prefix
func (container *Container) RegisterWebhookRoutes() {
	container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.WebhookHandler{}))
	container.WebhookHandler().RegisterRoutes(container.App(), container.AuthenticatedMiddleware())
}

// RegisterPhoneRoutes registers routes for the /phone prefix
func (container *Container) RegisterPhoneRoutes() {
	container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.PhoneHandler{}))
	container.PhoneHandler().RegisterRoutes(container.App(), container.AuthenticatedMiddleware())
	container.PhoneHandler().RegisterPhoneAPIKeyRoutes(container.App(), container.PhoneAPIKeyMiddleware(), container.AuthenticatedMiddleware())
}

// RegisterUserRoutes registers routes for the /users prefix
func (container *Container) RegisterUserRoutes() {
	container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.UserHandler{}))
	container.UserHandler().RegisterRoutes(container.App(), container.AuthenticatedMiddleware())
}

// RegisterMessageSendScheduleRoutes registers routes for the /send-schedules prefix
func (container *Container) RegisterMessageSendScheduleRoutes() {
	container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.MessageSendScheduleHandler{}))
	container.MessageSendScheduleHandler().RegisterRoutes(container.App(), container.AuthenticatedMiddleware())
}

// RegisterEventRoutes registers routes for the /events prefix
func (container *Container) RegisterEventRoutes() {
	container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.EventsHandler{}))
	container.EventsHandler().RegisterRoutes(container.App(), container.AuthenticatedMiddleware())
}

// RegisterSwaggerRoutes registers routes for swagger
func (container *Container) RegisterSwaggerRoutes() {
	container.logger.Debug(fmt.Sprintf("registering %T routes", swagger.HandlerDefault))
	container.App().Get("/*", swagger.New(swagger.Config{
		Title: docs.SwaggerInfo.Title,
		CustomScript: `
		document.addEventListener("DOMContentLoaded", function(event) {
			document.body.style.margin = '0';
			var links = document.querySelectorAll("link[rel~='icon']");
			links.forEach(function (link) {
				link.href = 'https://httpsms.com/favicon.ico';
			});
		});`,
	}))
}

// HeartbeatRepository registers a new instance of repositories.HeartbeatRepository
func (container *Container) HeartbeatRepository() repositories.HeartbeatRepository {
	switch os.Getenv("HEARTBEAT_DB_BACKEND") {
	case "mongodb":
		container.logger.Debug("creating MongoDB repositories.HeartbeatRepository")
		return repositories.NewMongoHeartbeatRepository(
			container.Logger(),
			container.Tracer(),
			container.MongoDB(),
		)
	default:
		container.logger.Debug("creating GORM repositories.HeartbeatRepository")
		return repositories.NewGormHeartbeatRepository(
			container.Logger(),
			container.Tracer(),
			container.DedicatedDB(),
		)
	}
}

// UserRepository registers a new instance of repositories.UserRepository
func (container *Container) UserRepository() repositories.UserRepository {
	container.logger.Debug("creating GORM repositories.UserRepository")
	return repositories.NewGormUserRepository(
		container.Logger(),
		container.Tracer(),
		container.UserRistrettoCache(),
		container.DB(),
	)
}

// PhoneRistrettoCache creates an in-memory *ristretto.Cache[string, *entities.Phone]
func (container *Container) PhoneRistrettoCache() *ristretto.Cache[string, *entities.Phone] {
	if container.phoneRistrettoCache != nil {
		return container.phoneRistrettoCache
	}
	container.logger.Debug(fmt.Sprintf("creating %T", container.phoneRistrettoCache))
	ristrettoCache, err := ristretto.NewCache[string, *entities.Phone](&ristretto.Config[string, *entities.Phone]{
		MaxCost:     5000,
		NumCounters: 5000 * 10,
		BufferItems: 64,
	})
	if err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, "cannot create phone ristretto cache"))
	}
	container.phoneRistrettoCache = ristrettoCache
	return container.phoneRistrettoCache
}

// UserRistrettoCache creates an in-memory *ristretto.Cache[string, entities.AuthContext]
func (container *Container) UserRistrettoCache() *ristretto.Cache[string, entities.AuthContext] {
	if container.userRistrettoCache != nil {
		return container.userRistrettoCache
	}
	container.logger.Debug(fmt.Sprintf("creating %T", container.userRistrettoCache))
	ristrettoCache, err := ristretto.NewCache[string, entities.AuthContext](&ristretto.Config[string, entities.AuthContext]{
		MaxCost:     5000,
		NumCounters: 5000 * 10,
		BufferItems: 64,
	})
	if err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, "cannot create user ristretto cache"))
	}
	container.userRistrettoCache = ristrettoCache
	return ristrettoCache
}

// InitializeTraceProvider initializes the open telemetry trace provider
func (container *Container) InitializeTraceProvider() func() {
	return container.initializeAxiomTraceProvider(container.version, container.projectID)
}

func (container *Container) initializeGoogleTraceProvider(version string, namespace string) func() {
	container.logger.Debug("initializing google trace meterProvider")

	traceExporter, err := cloudtrace.New(cloudtrace.WithProjectID(os.Getenv("GCP_PROJECT_ID")))
	if err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, "cannot create cloud trace traceExporter"))
	}

	tp := trace.NewTracerProvider(
		trace.WithBatcher(traceExporter),
		trace.WithSampler(trace.AlwaysSample()),
		trace.WithResource(container.OtelResources(version, namespace)),
	)
	otel.SetTracerProvider(tp)

	metricExporter, err := mexporter.New(mexporter.WithProjectID(os.Getenv("GCP_PROJECT_ID")))
	if err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, "cannot create cloud metric traceExporter"))
	}

	meterProvider := metric.NewMeterProvider(
		metric.WithReader(metric.NewPeriodicReader(metricExporter)),
		metric.WithResource(container.OtelResources(version, namespace)),
	)
	otel.SetMeterProvider(meterProvider)

	return func() {
		if err = metricExporter.Shutdown(context.Background()); err != nil {
			container.logger.Error(stacktrace.Propagate(err, "cannot shutdown cloud metric metric exporter"))
		}
		if err = traceExporter.Shutdown(context.Background()); err != nil {
			container.logger.Error(stacktrace.Propagate(err, "cannot shutdown cloud trace trace exporter"))
		}
	}
}

func (container *Container) initializeAxiomTraceProvider(version string, namespace string) func() {
	container.logger.Debug("initializing axiom trace provider")

	traceHeaders := map[string]string{
		"Authorization":   "Bearer " + os.Getenv("AXIOM_TOKEN"),
		"X-Axiom-Dataset": os.Getenv("AXIOM_DATASET_EVENTS"),
	}

	traceExporter, err := otlptracehttp.New(
		context.Background(),
		otlptracehttp.WithEndpoint("us-east-1.aws.edge.axiom.co"),
		otlptracehttp.WithHeaders(traceHeaders),
	)
	if err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, "cannot create axiom OTLP trace exporter"))
	}

	tp := trace.NewTracerProvider(
		trace.WithBatcher(traceExporter),
		trace.WithSampler(trace.AlwaysSample()),
		trace.WithResource(container.OtelResources(version, namespace)),
	)
	otel.SetTracerProvider(tp)

	otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
		propagation.TraceContext{},
		propagation.Baggage{},
	))

	metricHeaders := map[string]string{
		"Authorization":   "Bearer " + os.Getenv("AXIOM_TOKEN"),
		"X-Axiom-Dataset": os.Getenv("AXIOM_DATASET_METRICS"),
	}

	metricExporter, err := otlpmetrichttp.New(
		context.Background(),
		otlpmetrichttp.WithEndpoint("us-east-1.aws.edge.axiom.co"),
		otlpmetrichttp.WithHeaders(metricHeaders),
	)
	if err != nil {
		container.logger.Fatal(stacktrace.Propagate(err, "cannot create axiom OTLP metric exporter"))
	}

	meterProvider := metric.NewMeterProvider(
		metric.WithReader(metric.NewPeriodicReader(metricExporter)),
		metric.WithResource(container.OtelResources(version, namespace)),
	)
	otel.SetMeterProvider(meterProvider)

	return func() {
		if err := tp.Shutdown(context.Background()); err != nil {
			container.logger.Error(stacktrace.Propagate(err, "cannot shutdown axiom trace provider"))
		}
		if err := meterProvider.Shutdown(context.Background()); err != nil {
			container.logger.Error(stacktrace.Propagate(err, "cannot shutdown axiom meter provider"))
		}
	}
}

func (container *Container) initializeUptraceProvider(version string, namespace string) (flush func()) {
	container.logger.Debug("initializing uptrace provider")
	// Configure OpenTelemetry with sensible defaults.
	uptrace.ConfigureOpentelemetry(
		uptrace.WithDSN(os.Getenv("UPTRACE_DSN")),
		uptrace.WithServiceName(namespace),
		uptrace.WithServiceVersion(version),
		uptrace.WithDeploymentEnvironment(os.Getenv("ENV")),
	)

	// Send buffered spans and free resources.
	return func() {
		err := uptrace.Shutdown(context.Background())
		if err != nil {
			container.logger.Error(err)
		}
	}
}

func logger(skipFrameCount int) telemetry.Logger {
	fields := map[string]string{
		string(semconv.ServiceInstanceIDKey):     instanceID(),
		string(semconv.DeploymentEnvironmentKey): os.Getenv("ENV"),
	}

	return telemetry.NewZerologLogger(
		os.Getenv("GCP_PROJECT_ID"),
		fields,
		logDriver(skipFrameCount),
		nil,
	)
}

func logDriver(skipFrameCount int) *zerodriver.Logger {
	if isLocal() {
		return consoleLogger(skipFrameCount)
	}
	return axiomLogger(skipFrameCount)
}

func axiomLogger(skipFrameCount int) *zerodriver.Logger {
	axiomWriter, err := axiomzerolog.New(
		axiomzerolog.SetLevels([]zerolog.Level{zerolog.TraceLevel, zerolog.DebugLevel, zerolog.InfoLevel, zerolog.WarnLevel, zerolog.ErrorLevel, zerolog.PanicLevel, zerolog.FatalLevel, zerolog.NoLevel}),
		axiomzerolog.SetDataset(os.Getenv("AXIOM_DATASET_EVENTS")),
	)
	if err != nil {
		log.Fatal(stacktrace.Propagate(err, "cannot create axiom zerolog writer"))
	}

	zl := zerolog.New(axiomWriter).With().Timestamp().CallerWithSkipFrameCount(skipFrameCount).Logger()
	return &zerodriver.Logger{Logger: &zl}
}

func instanceID() string {
	h, err := os.Hostname()
	if err != nil {
		h = strconv.Itoa(os.Getpid())
	}
	if metadata.OnGCE() {
		return getGCEInstanceID(h)
	}
	return h
}

func getGCEInstanceID(hostname string) string {
	instanceID, err := metadata.InstanceIDWithContext(context.Background())
	if err != nil {
		return hostname
	}
	return instanceID
}

func consoleLogger(skipFrameCount int) *zerodriver.Logger {
	l := zerolog.New(
		zerolog.ConsoleWriter{
			Out: os.Stderr,
		},
	).With().Timestamp().CallerWithSkipFrameCount(skipFrameCount).Logger()
	return &zerodriver.Logger{
		Logger: &l,
	}
}

func isLocal() bool {
	return os.Getenv("ENV") == "local"
}
