import { Injectable } from '@nestjs/common';
import { randomUUID } from 'crypto';
import type { Request } from 'express';
import type {
  RawEventEnvelope,
  NormalizedEvent,
  EdgeContext,
  IngestKind,
} from '@lilith/analytics';

import { RedisRouter } from './redis-router.service';
import { TrackViewDto } from '../dto/track-view.dto';
import { TrackEngagementDto } from '../dto/track-engagement.dto';
import { TrackInteractionBatchDto } from '../dto/track-interaction.dto';
import {
  TrackEventDto,
  TrackBatchDto,
  TrackConversionDto,
  TrackFunnelStepDto,
  TrackRegistrationFunnelDto,
} from '../dto/track-event.dto';

export interface CaptureResult {
  success: boolean;
  eventId: string | null;
}

export interface BatchCaptureResult {
  success: boolean;
  count: number;
}

/**
 * Edge capture. Validates already happened at the controller (class-validator);
 * here we do the pure per-endpoint normalization that used to live in the
 * collector's TrackingService/controller, mint an edge event id + receive time,
 * and durably enqueue via {@link RedisRouter}. No database, no enrichment — all
 * of that is deferred to the black-side ingest-writer.
 */
@Injectable()
export class CaptureService {
  constructor(private readonly router: RedisRouter) {}

  async trackView(dto: TrackViewDto, request: Request): Promise<CaptureResult> {
    const pageUrl = dto.pageUrl ?? dto.contentId ?? '';
    return this.one('view', request, {
      eventType: 'pageview',
      sessionId: dto.sessionId,
      userId: dto.userId ?? null,
      pageUrl,
      referrer: dto.referrer ?? null,
      isView: true,
      clientDevice: (dto.clientDevice as Record<string, unknown> | undefined) ?? null,
      attribution: dto.attribution
        ? {
            utmSource: dto.attribution.utmSource,
            utmMedium: dto.attribution.utmMedium,
            utmCampaign: dto.attribution.utmCampaign,
            utmContent: dto.attribution.utmContent,
            utmTerm: dto.attribution.utmTerm,
            referrer: dto.attribution.referrer ?? dto.referrer,
          }
        : { referrer: dto.referrer },
      metadata: {
        ...dto.metadata,
        ...(dto.contentType ? { contentType: dto.contentType } : {}),
        ...(dto.app ? { app: dto.app } : {}),
        ...(dto.duration !== undefined ? { duration: dto.duration } : {}),
      },
    });
  }

  async trackEngagement(dto: TrackEngagementDto, request: Request): Promise<CaptureResult> {
    return this.one('engagement', request, {
      eventType: `engagement_${dto.metricType}`,
      sessionId: dto.userId, // engagement events are user-scoped
      userId: dto.userId,
      isView: false,
      metadata: {
        metricType: dto.metricType,
        targetId: dto.targetId,
        targetType: dto.targetType,
        ...dto.metadata,
      },
    });
  }

  async trackInteraction(dto: TrackInteractionBatchDto, request: Request): Promise<BatchCaptureResult> {
    return this.many(
      'interaction',
      request,
      dto.events.map((event) => ({
        eventType: event.type,
        sessionId: event.sessionId,
        userId: event.userId ?? null,
        metadata: event.data,
        timestamp: event.timestamp ?? null,
        isView: false,
      })),
    );
  }

  async trackEvent(dto: TrackEventDto, request: Request): Promise<CaptureResult> {
    return this.one('event', request, {
      eventType: dto.eventType,
      sessionId: dto.sessionId,
      userId: dto.userId ?? null,
      pageUrl: dto.pageUrl ?? null,
      metadata: dto.metadata ?? null,
      timestamp: dto.timestamp ?? null,
      isView: false,
    });
  }

  async trackBatch(dto: TrackBatchDto, request: Request): Promise<BatchCaptureResult> {
    return this.many(
      'batch',
      request,
      dto.events.map((e) => ({
        eventType: e.eventType,
        sessionId: e.sessionId,
        userId: e.userId ?? null,
        pageUrl: e.pageUrl ?? null,
        metadata: e.metadata ?? null,
        timestamp: e.timestamp ?? null,
        isView: false,
      })),
    );
  }

  async trackConversion(dto: TrackConversionDto, request: Request): Promise<CaptureResult> {
    return this.one('conversion', request, {
      eventType: 'conversion',
      sessionId: dto.sessionId,
      userId: dto.userId ?? null,
      isView: false,
      metadata: {
        conversionType: dto.conversionType,
        value: dto.value,
        currency: dto.currency,
        ...dto.metadata,
      },
    });
  }

  async trackFunnelStep(dto: TrackFunnelStepDto, request: Request): Promise<CaptureResult> {
    return this.one('funnel', request, {
      eventType: 'funnel_step',
      sessionId: dto.sessionId,
      userId: dto.userId ?? null,
      isView: false,
      metadata: {
        funnelId: dto.funnelId,
        stepId: dto.stepId,
        stepOrder: dto.stepOrder,
        ...dto.metadata,
      },
    });
  }

  async trackRegistrationFunnel(
    dto: TrackRegistrationFunnelDto,
    request: Request,
  ): Promise<CaptureResult> {
    const parsed = Date.parse(dto.timestamp);
    return this.one('registration-funnel', request, {
      eventType: `registration_funnel_${dto.event}`,
      sessionId: dto.sessionId,
      userId: dto.userId ?? null,
      pageUrl: dto.route ?? null,
      timestamp: Number.isNaN(parsed) ? null : parsed,
      isView: false,
      metadata: {
        audience: dto.audience,
        userType: dto.userType,
        registrationPath: dto.registrationPath,
        registrationType: dto.registrationType,
        entryReferrer: dto.entryReferrer,
        referrer: dto.referrer,
      },
    });
  }

  // ── internals ──────────────────────────────────────────────────────────────

  private async one(
    kind: IngestKind,
    request: Request,
    event: NormalizedEvent,
  ): Promise<CaptureResult> {
    const eventId = randomUUID();
    await this.router.enqueue(this.envelope(eventId, kind, request, event));
    return { success: true, eventId };
  }

  private async many(
    kind: IngestKind,
    request: Request,
    events: NormalizedEvent[],
  ): Promise<BatchCaptureResult> {
    const edge = this.buildEdge(request);
    const receivedAt = new Date().toISOString();
    const results = await Promise.allSettled(
      events.map((event) =>
        this.router.enqueue({
          eventId: randomUUID(),
          kind,
          receivedAt,
          edge,
          payload: event,
        }),
      ),
    );
    const count = results.filter((r) => r.status === 'fulfilled').length;
    return { success: count === events.length, count };
  }

  private envelope(
    eventId: string,
    kind: IngestKind,
    request: Request,
    event: NormalizedEvent,
  ): RawEventEnvelope {
    return {
      eventId,
      kind,
      receivedAt: new Date().toISOString(),
      edge: this.buildEdge(request),
      payload: event,
    };
  }

  private buildEdge(request: Request): EdgeContext {
    const header = (name: string): string | undefined => {
      const v = request.headers[name];
      return Array.isArray(v) ? v[0] : v;
    };
    const forwardedFor = header('x-forwarded-for');
    const ip =
      forwardedFor?.split(',')[0]?.trim() ?? header('x-real-ip') ?? request.socket.remoteAddress;
    return {
      ip: ip ?? undefined,
      ua: header('user-agent'),
      lang: header('accept-language'),
      headers: {
        origin: header('origin'),
        referer: header('referer'),
        host: header('host'),
      },
    };
  }
}
