import type {
  ErrorReportingLevel,
  ErrorReportingService,
  ErrorReportingUser,
} from './error-reporting-service';
import type { NotificationService } from '../notifications/notification-service';
import type { LoggerService } from '../logging/logger-service';

export type GlitchtipErrorReportingServiceOptions = {
  enabled: boolean;
  dsn?: string;
  tracesSampleRate?: number;
};

// GlitchTip speaks the Sentry protocol; @sentry/nextjs can send to GlitchTip via DSN.
// Sentry is dynamically imported to avoid a ~100KB main-thread execution penalty on initial load.
export class GlitchtipErrorReportingService implements ErrorReportingService {
  private logger: LoggerService;
  private sentryPromise: Promise<typeof import('@sentry/nextjs')> | null = null;

  constructor(
    private readonly options: GlitchtipErrorReportingServiceOptions,
    logger: LoggerService,
    private readonly notifications?: NotificationService,
  ) {
    this.logger = logger.child({ component: 'error-reporting-glitchtip' });

    if (this.options.enabled) {
      if (typeof window !== 'undefined') {
        // On client-side, wait until idle before fetching Sentry
        if ('requestIdleCallback' in window) {
          window.requestIdleCallback(() => {
            this.getSentry();
          });
        } else {
          setTimeout(() => {
            this.getSentry();
          }, 3000);
        }
      } else {
        // Pre-fetch on server-side
        this.getSentry();
      }
    }
  }

  private getSentry(): Promise<typeof import('@sentry/nextjs')> {
    if (!this.sentryPromise) {
      this.sentryPromise = import('@sentry/nextjs').then((Sentry) => {
        // Client-side initialization must happen here since sentry.client.config.ts is empty
        if (typeof window !== 'undefined' && this.options.enabled) {
          Sentry.init({
            dsn: this.options.dsn || 'https://public@errors.infra.mintel.me/1',
            tunnel: '/errors/api/relay',
            enabled: true,
            tracesSampleRate: this.options.tracesSampleRate ?? 0.1,
            replaysOnErrorSampleRate: 1.0,
            replaysSessionSampleRate: 0.1,
          });
        }
        return Sentry;
      });
    }
    return this.sentryPromise;
  }

  async captureException(error: unknown, context?: Record<string, unknown>) {
    if (!this.options.enabled) return undefined;

    // Send to Gotify if it's considered critical or if we just want all exceptions there
    if (this.notifications) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      const contextStr = context ? `\nContext: ${JSON.stringify(context, null, 2)}` : '';

      await this.notifications.notify({
        title: '🔥 Critical Error Captured',
        message: `Error: ${errorMessage}${contextStr}`,
        priority: 7,
      });
    }

    const Sentry = await this.getSentry();
    return Sentry.captureException(error, context as any) as any;
  }

  async captureMessage(message: string, level: ErrorReportingLevel = 'error') {
    if (!this.options.enabled) return undefined;

    // Send 404s and critical messages straight to Gotify
    if (
      this.notifications &&
      (level === 'error' || level === 'fatal' || message.includes('Route Not Found'))
    ) {
      await this.notifications.notify({
        title: level === 'warning' ? '⚠️ Warning Captured' : '🔥 Critical Message Captured',
        message: message,
        priority: level === 'warning' ? 5 : 7,
      });
    }

    const Sentry = await this.getSentry();
    return Sentry.captureMessage(message, level as any) as any;
  }

  setUser(user: ErrorReportingUser | null) {
    if (!this.options.enabled) return;
    this.getSentry().then((Sentry) => Sentry.setUser(user as any));
  }

  setTag(key: string, value: string) {
    if (!this.options.enabled) return;
    this.getSentry().then((Sentry) => Sentry.setTag(key, value));
  }

  withScope<T>(fn: () => T, _context?: Record<string, unknown>): T {
    if (!this.options.enabled) return fn();

    // Since withScope mandates executing fn() synchronously to return T,
    // and Sentry load is async, if context mapping is absolutely required
    // for this feature we would need an async API.
    // For now we degrade gracefully by just executing the function.
    return fn();
  }
}
