import {
  Controller,
  Header,
  Request,
  Sse,
  Inject,
  HttpException,
  HttpStatus,
  UseGuards,
} from "@nestjs/common";
import type { MessageEvent } from "@nestjs/common";
import { Observable, merge } from "rxjs";
import { finalize } from "rxjs/operators";
import type { RequestWithUser } from "@bff/modules/auth/auth.types.js";
import { RealtimeService } from "@bff/infra/realtime/realtime.service.js";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
import { Logger } from "nestjs-pino";
import { RateLimit, RateLimitGuard } from "@bff/core/rate-limiting/index.js";
import { RealtimeConnectionLimiterService } from "./realtime-connection-limiter.service.js";

@Controller("events")
export class RealtimeController {
  constructor(
    private readonly realtime: RealtimeService,
    private readonly mappings: MappingsService,
    private readonly limiter: RealtimeConnectionLimiterService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * Account-scoped realtime events stream.
   *
   * Single predictable SSE entrypoint for the portal.
   * Backed by Redis pub/sub for multi-instance delivery.
   */
  @Sse()
  @UseGuards(RateLimitGuard)
  @RateLimit({ limit: 30, ttl: 60 }) // protect against reconnect storms / refresh spam
  @Header("Cache-Control", "private, no-store")
  @Header("X-Accel-Buffering", "no") // nginx: disable response buffering for SSE
  async stream(@Request() req: RequestWithUser): Promise<Observable<MessageEvent>> {
    if (!this.limiter.tryAcquire(req.user.id)) {
      throw new HttpException(
        "Too many concurrent realtime connections",
        HttpStatus.TOO_MANY_REQUESTS
      );
    }

    const mapping = await this.mappings.findByUserId(req.user.id);

    // Intentionally log minimal info for debugging connection issues.
    this.logger.log("Account realtime stream connected", {
      userId: req.user.id,
      hasMapping: Boolean(mapping),
      sfAccountIdTail: mapping?.sfAccountId.slice(-4) ?? null,
    });

    const accountStream = this.realtime.subscribe(
      mapping ? `account:sf:${mapping.sfAccountId}` : "account:unknown",
      {
        // Always provide a single predictable ready + heartbeat for the main account stream.
        readyEvent: "account.stream.ready",
        heartbeatEvent: "account.stream.heartbeat",
        heartbeatMs: 30000,
      }
    );

    const globalServicesStream = this.realtime.subscribe("global:services", {
      // Avoid duplicate ready/heartbeat noise on the combined stream.
      readyEvent: null,
      heartbeatEvent: null,
      heartbeatMs: 0,
    });

    return merge(accountStream, globalServicesStream).pipe(
      finalize(() => {
        this.limiter.release(req.user.id);
        this.logger.debug("Account realtime stream disconnected", {
          userId: req.user.id,
        });
      })
    );
  }
}
