import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
  Inject,
  Logger,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { type Observable, tap } from 'rxjs';
import { AnalyticsService } from '../services';
import { TRACK_METADATA_KEY, NO_TRACK_METADATA_KEY } from '../decorators';
import { ANALYTICS_OPTIONS, type AnalyticsModuleOptions } from '../module';
import type { TrackOptions, TrackContext } from '../types';

@Injectable()
export class AnalyticsInterceptor implements NestInterceptor {
  private readonly logger = new Logger(AnalyticsInterceptor.name);

  constructor(
    private readonly reflector: Reflector,
    private readonly analyticsService: AnalyticsService,
    @Inject(ANALYTICS_OPTIONS) private readonly options: AnalyticsModuleOptions
  ) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    if (!this.options.enabled) {
      return next.handle();
    }

    const noTrack = this.reflector.get<boolean>(
      NO_TRACK_METADATA_KEY,
      context.getHandler()
    );

    if (noTrack) {
      return next.handle();
    }

    const trackOptions = this.reflector.get<TrackOptions>(
      TRACK_METADATA_KEY,
      context.getHandler()
    );

    // If no @Track decorator and not auto-tracking, skip
    if (!trackOptions) {
      return next.handle();
    }

    const request = context.switchToHttp().getRequest();
    const startTime = Date.now();

    return next.handle().pipe(
      tap({
        next: () => {
          const response = context.switchToHttp().getResponse();
          const executionTime = Date.now() - startTime;

          const trackContext: TrackContext = {
            request: {
              method: request.method,
              url: request.url,
              path: request.route?.path || request.path,
              body: trackOptions.includeBody ? request.body : undefined,
              params: trackOptions.includeParams ? request.params : undefined,
              query: trackOptions.includeQuery ? request.query : undefined,
              headers: request.headers,
              ip: request.ip,
              user: request.user,
            },
            response: {
              statusCode: response.statusCode,
            },
            executionTime,
          };

          const eventName =
            trackOptions.event || `${request.method.toLowerCase()}.${context.getHandler().name}`;

          const properties = trackOptions.extractProperties
            ? trackOptions.extractProperties(trackContext)
            : this.extractDefaultProperties(trackContext, trackOptions);

          this.analyticsService
            .track({
              type: 'custom',
              properties: {
                name: eventName,
                category: trackOptions.category,
                ...properties,
              },
            })
            .catch((error: unknown) => {
              if (this.options.debug) {
                this.logger.error('Failed to track event:', error);
              }
            });
        },
        error: (error: unknown) => {
          if (this.options.debug) {
            this.logger.error('Request failed:', error);
          }
        },
      })
    );
  }

  private extractDefaultProperties(
    context: TrackContext,
    options: TrackOptions
  ): Record<string, unknown> {
    const properties: Record<string, unknown> = {
      method: context.request.method,
      path: context.request.path,
      statusCode: context.response?.statusCode,
      executionTime: context.executionTime,
      userId: context.request.user?.id,
    };

    if (options.includeBody && context.request.body) {
      properties['body'] = context.request.body;
    }

    if (options.includeParams && context.request.params) {
      properties['params'] = context.request.params;
    }

    if (options.includeQuery && context.request.query) {
      properties['query'] = context.request.query;
    }

    return properties;
  }
}
