import { AppServices } from "./app-services";
import { NoopAnalyticsService } from "./analytics/noop-analytics-service";
import { MemoryCacheService } from "./cache/memory-cache-service";
import { GlitchtipErrorReportingService } from "./errors/glitchtip-error-reporting-service";
import { NoopErrorReportingService } from "./errors/noop-error-reporting-service";
import { NoopLoggerService } from "./logging/noop-logger-service";
import { PinoLoggerService } from "./logging/pino-logger-service";
import { NoopNotificationService } from "./notifications/gotify-notification-service";
import { config, getMaskedConfig } from "../config";

/**
 * Singleton instance of AppServices.
 *
 * In Next.js, module singletons are per-process (server) and per-tab (client).
 * This is sufficient for a small service layer and provides better performance
 * than creating new instances on every request.
 *
 * @private
 */
let singleton: AppServices | undefined;

/**
 * Get the application services singleton.
 *
 * This function creates and caches the application services, including:
 * - Analytics service (Umami or no-op)
 * - Error reporting service (GlitchTip/Sentry or no-op)
 * - Cache service (in-memory)
 *
 * The services are configured based on environment variables:
 * - `UMAMI_WEBSITE_ID` - Enables Umami analytics
 * - `NEXT_PUBLIC_SENTRY_DSN` - Enables client-side error reporting
 * - `SENTRY_DSN` - Enables server-side error reporting
 *
 * @returns {AppServices} The application services singleton
 *
 * @example
 * ```typescript
 * // Get services in a client component
 * import { getAppServices } from '@/lib/services/create-services';
 *
 * const services = getAppServices();
 * services.analytics.track('button_click', { button_id: 'cta' });
 * ```
 *
 * @example
 * ```typescript
 * // Get services in a server component or API route
 * import { getAppServices } from '@/lib/services/create-services';
 *
 * const services = getAppServices();
 * await services.cache.set('key', 'value');
 * ```
 *
 * @example
 * ```typescript
 * // Automatic service selection based on environment
 * // If NEXT_PUBLIC_UMAMI_WEBSITE_ID is set:
 * //   services.analytics = UmamiAnalyticsService
 * // If not set:
 * //   services.analytics = NoopAnalyticsService (safe no-op)
 * ```
 *
 * @see {@link UmamiAnalyticsService} for analytics implementation
 * @see {@link NoopAnalyticsService} for no-op fallback
 * @see {@link GlitchtipErrorReportingService} for error reporting
 * @see {@link MemoryCacheService} for caching
 */
export function getAppServices(): AppServices {
  // Return cached instance if available
  if (singleton) return singleton;

  // Create logger first to log initialization
  const logger =
    typeof window === "undefined"
      ? new PinoLoggerService("server")
      : new NoopLoggerService();

  // Log initialization
  if (typeof window === "undefined") {
    // Server-side
    logger.info("Initializing server application services", {
      environment: getMaskedConfig(),
      timestamp: new Date().toISOString(),
    });
  } else {
    // Client-side
    logger.info("Initializing client application services", {
      environment: getMaskedConfig(),
      timestamp: new Date().toISOString(),
    });
  }

  // Determine which services to enable based on environment variables
  const umamiEnabled = config.analytics.umami.enabled;
  const sentryEnabled = config.errors.glitchtip.enabled;

  logger.info("Service configuration", {
    umamiEnabled,
    sentryEnabled,
    isServer: typeof window === "undefined",
  });

  // Create analytics service (Umami or no-op)
  // Use dynamic import to avoid importing server-only code in client components
  const analytics = umamiEnabled
    ? (() => {
        const { UmamiAnalyticsService } =
          // eslint-disable-next-line @typescript-eslint/no-require-imports
          require("./analytics/umami-analytics-service");
        return new UmamiAnalyticsService({ enabled: true });
      })()
    : new NoopAnalyticsService();

  if (umamiEnabled) {
    logger.info("Umami analytics service initialized");
  } else {
    logger.info("Noop analytics service initialized (analytics disabled)");
  }

  // Create error reporting service (GlitchTip/Sentry or no-op)
  const errors = sentryEnabled
    ? new GlitchtipErrorReportingService({ enabled: true })
    : new NoopErrorReportingService();

  if (sentryEnabled) {
    logger.info(
      `GlitchTip error reporting service initialized (${typeof window === "undefined" ? "server" : "client"})`,
    );
  } else {
    logger.info(
      "Noop error reporting service initialized (error reporting disabled)",
    );
  }

  // IMPORTANT: This module is imported by client components.
  // Do not import Node-only modules (like the `redis` client) here.
  // Use [`getServerAppServices()`](lib/services/create-services.server.ts:1) on the server.
  const cache = new MemoryCacheService();
  logger.info("Memory cache service initialized");

  logger.info("Pino logger service initialized", {
    name: typeof window === "undefined" ? "server" : "client",
    level: config.logging.level,
  });

  // Create and cache the singleton
  const notifications = new NoopNotificationService();
  singleton = new AppServices(analytics, errors, cache, logger, notifications);

  logger.info("All application services initialized successfully");

  return singleton;
}
