import type { AnalyticsClient } from '../analytics-client';
import type { ViewEventData, EngagementEventData } from '../types';
import type { TrackingContext } from './types';

/**
 * Helper function to track a service method call.
 *
 * Use this in service methods that don't have access to HTTP context
 * but still need to track analytics events.
 *
 * @param client - Analytics client instance
 * @param context - Tracking context
 * @param options - Event options
 *
 * @example
 * ```typescript
 * @Injectable()
 * export class ProductsService {
 *   constructor(
 *     @Inject(ANALYTICS_CLIENT) private analytics: AnalyticsClient,
 *   ) {}
 *
 *   async findOne(id: string, userId?: string) {
 *     const product = await this.productsRepository.findOne(id);
 *
 *     // Track the view
 *     trackServiceCall(this.analytics, {
 *       method: 'findOne',
 *       className: 'ProductsService',
 *       args: [id, userId],
 *     }, {
 *       eventType: 'view',
 *       contentType: 'product',
 *       contentId: id,
 *       userId,
 *     });
 *
 *     return product;
 *   }
 * }
 * ```
 */
export function trackServiceCall(
  client: AnalyticsClient,
  _context: Pick<TrackingContext, 'method' | 'className' | 'args'>,
  options: {
    eventType: 'view';
    contentType: ViewEventData['contentType'];
    contentId: string;
    userId?: string;
    metadata?: Record<string, unknown>;
  } | {
    eventType: 'engagement';
    metricType: EngagementEventData['metricType'];
    targetType: EngagementEventData['targetType'];
    targetId: string;
    userId: string;
    metadata?: Record<string, unknown>;
  },
): void {
  try {
    if (options.eventType === 'view') {
      const viewData: Omit<ViewEventData, 'app' | 'sessionId'> = {
        contentId: options.contentId,
        contentType: options.contentType,
        userId: options.userId,
      };

      client.trackView(viewData);
    } else if (options.eventType === 'engagement') {
      const engagementData: EngagementEventData = {
        userId: options.userId,
        metricType: options.metricType,
        targetId: options.targetId,
        targetType: options.targetType,
        metadata: options.metadata,
      };

      client.trackEngagement(engagementData);
    }
  } catch (error) {
    console.error('Failed to track service call:', error);
  }
}

/**
 * Helper function to track API endpoint calls with full HTTP context.
 *
 * Use this in controllers or middleware when you want manual control
 * over analytics tracking instead of using the decorator.
 *
 * @param client - Analytics client instance
 * @param request - HTTP request object
 * @param options - Event options
 *
 * @example
 * ```typescript
 * @Controller('streams')
 * export class StreamsController {
 *   constructor(
 *     @Inject(ANALYTICS_CLIENT) private analytics: AnalyticsClient,
 *   ) {}
 *
 *   @Get(':id/watch')
 *   async watchStream(
 *     @Param('id') id: string,
 *     @Req() req: Request,
 *   ) {
 *     const stream = await this.streamsService.findOne(id);
 *
 *     // Track the stream view with full context
 *     trackApiEndpoint(this.analytics, req, {
 *       eventType: 'view',
 *       contentType: 'stream',
 *       contentId: id,
 *     });
 *
 *     return stream;
 *   }
 * }
 * ```
 */
export function trackApiEndpoint(
  client: AnalyticsClient,
  request: {
    headers?: Record<string, string | string[]>;
    ip?: string;
    user?: { id: string; [key: string]: unknown };
    [key: string]: unknown;
  },
  options: {
    eventType: 'view';
    contentType: ViewEventData['contentType'];
    contentId: string;
    metadata?: Record<string, unknown>;
  } | {
    eventType: 'engagement';
    metricType: EngagementEventData['metricType'];
    targetType: EngagementEventData['targetType'];
    targetId: string;
    metadata?: Record<string, unknown>;
  },
): void {
  try {
    const userId = request.user?.id;

    if (options.eventType === 'view') {
      const viewData: Omit<ViewEventData, 'app' | 'sessionId'> = {
        contentId: options.contentId,
        contentType: options.contentType,
        userId,
        referrer: request.headers?.referer as string | undefined,
        ipAddress: request.ip,
      };

      client.trackView(viewData);
    } else if (options.eventType === 'engagement') {
      if (!userId) {
        console.warn('Skipping engagement tracking: userId is required');
        return;
      }

      const engagementData: EngagementEventData = {
        userId,
        metricType: options.metricType,
        targetId: options.targetId,
        targetType: options.targetType,
        metadata: options.metadata,
      };

      client.trackEngagement(engagementData);
    }
  } catch (error) {
    console.error('Failed to track API endpoint:', error);
  }
}

/**
 * Create a tracking middleware for express-style middleware chains.
 *
 * This can be used with NestJS middleware or express middleware.
 *
 * @param client - Analytics client instance
 * @param options - Default tracking options
 *
 * @example
 * ```typescript
 * @Injectable()
 * export class AnalyticsMiddleware implements NestMiddleware {
 *   constructor(
 *     @Inject(ANALYTICS_CLIENT) private analytics: AnalyticsClient,
 *   ) {}
 *
 *   use(req: Request, res: Response, next: NextFunction) {
 *     // Track page views for all GET requests
 *     if (req.method === 'GET' && req.params.id) {
 *       trackApiEndpoint(this.analytics, req, {
 *         eventType: 'view',
 *         contentType: 'post',
 *         contentId: req.params.id,
 *       });
 *     }
 *     next();
 *   }
 * }
 * ```
 */
export function createTrackingMiddleware(
  client: AnalyticsClient,
  options?: {
    /**
     * Filter function to determine if request should be tracked
     */
    shouldTrack?: (request: unknown) => boolean;
    /**
     * Extract content ID from request
     */
    extractContentId?: (request: unknown) => string | undefined;
    /**
     * Default content type
     */
    defaultContentType?: ViewEventData['contentType'];
  },
) {
  return (req: unknown, _res: unknown, next: () => void) => {
    try {
      const shouldTrack = options?.shouldTrack?.(req) ?? true;

      if (!shouldTrack) {
        next();
        return;
      }

      const request = req as {
        headers?: Record<string, string | string[]>;
        ip?: string;
        user?: { id: string };
      };

      const contentId = options?.extractContentId?.(req);

      if (contentId && options?.defaultContentType) {
        trackApiEndpoint(client, request, {
          eventType: 'view',
          contentType: options.defaultContentType,
          contentId,
        });
      }
    } catch (error) {
      console.error('Tracking middleware error:', error);
    }

    next();
  };
}
