import { Injectable, Inject, OnModuleDestroy } from '@nestjs/common';
import { ANALYTICS_OPTIONS, type AnalyticsModuleOptions } from '../module';
import type { AnalyticsEvent } from '../types';
import type { TrackEventPayload } from '../../types';

@Injectable()
export class AnalyticsService implements OnModuleDestroy {
  private eventQueue: AnalyticsEvent[] = [];
  private flushTimer: ReturnType<typeof setInterval> | null = null;

  constructor(
    @Inject(ANALYTICS_OPTIONS) private readonly options: AnalyticsModuleOptions
  ) {
    this.startFlushTimer();
  }

  async onModuleDestroy(): Promise<void> {
    if (this.flushTimer) {
      clearInterval(this.flushTimer);
    }
    await this.flush();
  }

  /**
   * Track an analytics event
   */
  async track(event: AnalyticsEvent): Promise<void> {
    if (!this.options.enabled) {
      return;
    }

    const fullEvent: AnalyticsEvent = {
      ...event,
      timestamp: event.timestamp || new Date().toISOString(),
      sessionId: event.sessionId || 'server',
    };

    if (this.options.debug) {
      console.log('[Analytics] Track:', fullEvent);
    }

    this.eventQueue.push(fullEvent);

    const maxSize = this.options.batch?.maxSize ?? 100;
    if (this.eventQueue.length >= maxSize) {
      await this.flush();
    }
  }

  /**
   * Track a page view event
   */
  async trackPageView(
    url: string,
    options: { title?: string; referrer?: string; userId?: string } = {}
  ): Promise<void> {
    const parsedUrl = new URL(url, 'http://localhost');
    const queryParams: Record<string, string> = {};
    parsedUrl.searchParams.forEach((value, key) => {
      queryParams[key] = value;
    });
    await this.track({
      type: 'page_view',
      userId: options.userId,
      properties: {
        url,
        title: options.title,
        referrer: options.referrer,
        path: parsedUrl.pathname,
        queryParams,
      },
    });
  }

  /**
   * Track a conversion event
   */
  async trackConversion(
    goalId: string,
    goalName: string,
    options: { value?: number; currency?: string; userId?: string } = {}
  ): Promise<void> {
    await this.track({
      type: 'conversion',
      userId: options.userId,
      properties: {
        goalId,
        goalName,
        value: options.value,
        currency: options.currency,
      },
    });
  }

  /**
   * Track a revenue event
   */
  async trackRevenue(
    transactionId: string,
    amount: number,
    currency: string,
    options: { productIds?: string[]; category?: string; userId?: string } = {}
  ): Promise<void> {
    await this.track({
      type: 'revenue',
      userId: options.userId,
      properties: {
        transactionId,
        amount,
        currency,
        productIds: options.productIds,
        category: options.category,
      },
    });
  }

  /**
   * Track a custom event
   */
  async trackCustom(
    name: string,
    properties: Record<string, unknown> = {},
    options: { category?: string; userId?: string } = {}
  ): Promise<void> {
    await this.track({
      type: 'custom',
      userId: options.userId,
      properties: {
        name,
        category: options.category,
        ...properties,
      },
    });
  }

  /**
   * Flush queued events to the collector
   */
  async flush(): Promise<void> {
    if (this.eventQueue.length === 0) {
      return;
    }

    const eventsToSend = [...this.eventQueue];
    this.eventQueue = [];

    try {
      const payload: TrackEventPayload = {
        // Cast events to the expected type - they match the interface
        events: eventsToSend as unknown as TrackEventPayload['events'],
      };

      const response = await fetch(this.options.collector.url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...(this.options.collector.apiKey && {
            Authorization: `Bearer ${this.options.collector.apiKey}`,
          }),
        },
        body: JSON.stringify(payload),
        signal: AbortSignal.timeout(this.options.collector.timeout ?? 5000),
      });

      if (!response.ok) {
        throw new Error(`Collector responded with ${response.status}`);
      }

      if (this.options.debug) {
        console.log(`[Analytics] Flushed ${eventsToSend.length} events`);
      }
    } catch (error) {
      // Re-queue events on failure
      this.eventQueue = [...eventsToSend, ...this.eventQueue];

      if (this.options.debug) {
        console.error('[Analytics] Failed to flush events:', error);
      }

      // Optionally retry based on config
      const retries = this.options.collector.retries ?? 0;
      if (retries > 0) {
        // Implement retry logic if needed
      }
    }
  }

  private startFlushTimer(): void {
    const maxWait = this.options.batch?.maxWait ?? 10000;
    if (maxWait > 0) {
      this.flushTimer = setInterval(() => {
        this.flush().catch(() => {
          // Errors are logged in flush()
        });
      }, maxWait);
    }
  }
}
