/**
 * Salesforce Pub/Sub Client Service
 *
 * Provides shared client initialization and connection management for both
 * CDC (Change Data Capture) and Platform Event subscribers.
 *
 * This service handles:
 * - Client instantiation with proper auth
 * - Connection lifecycle management
 * - Graceful shutdown
 */

import { Injectable, Inject } from "@nestjs/common";
import type { OnModuleDestroy } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Logger } from "nestjs-pino";
import PubSubApiClientPkg from "salesforce-pubsub-api-client";
import { SalesforceConnection } from "../../services/salesforce-connection.service.js";
import type { PubSubClient, PubSubClientConstructor, PubSubCallback } from "./pubsub.types.js";
import { parseNumRequested } from "./pubsub.utils.js";

@Injectable()
export class PubSubClientService implements OnModuleDestroy {
  private client: PubSubClient | null = null;
  private pubSubCtor: PubSubClientConstructor | null = null;
  private readonly numRequested: number;

  constructor(
    private readonly config: ConfigService,
    private readonly sfConnection: SalesforceConnection,
    @Inject(Logger) private readonly logger: Logger
  ) {
    this.numRequested = parseNumRequested(
      this.config.get<string>("SF_PUBSUB_NUM_REQUESTED"),
      25,
      (msg, ctx) => this.logger.warn(msg, ctx)
    );
  }

  async onModuleDestroy(): Promise<void> {
    await this.close();
  }

  /**
   * Get the default number of events to request per subscription
   */
  getNumRequested(): number {
    return this.numRequested;
  }

  /**
   * Ensure a connected Pub/Sub client is available
   */
  async ensureClient(): Promise<PubSubClient> {
    if (this.client) {
      return this.client;
    }

    const ctor = this.loadPubSubCtor();

    await this.sfConnection.connect();
    const accessToken = this.sfConnection.getAccessToken();
    const instanceUrl = this.sfConnection.getInstanceUrl();

    if (!accessToken || !instanceUrl) {
      throw new Error("Salesforce access token or instance URL missing for Pub/Sub client");
    }

    const pubSubEndpoint =
      this.config.get<string>("SF_PUBSUB_ENDPOINT") || "api.pubsub.salesforce.com:7443";

    const client = new ctor({
      authType: "user-supplied",
      accessToken,
      instanceUrl,
      pubSubEndpoint,
    });

    await client.connect();
    this.client = client;

    this.logger.log("Salesforce Pub/Sub client connected", { pubSubEndpoint });

    return client;
  }

  /**
   * Subscribe to a channel with diagnostic logging
   */
  async subscribe(channel: string, handler: PubSubCallback, label: string): Promise<void> {
    this.logger.log("Subscribing to Salesforce channel", {
      channel,
      label,
      numRequested: this.numRequested,
    });

    try {
      const client = await this.ensureClient();
      await client.subscribe(channel, handler, this.numRequested);

      this.logger.log("Successfully subscribed to Salesforce channel", {
        channel,
        label,
      });
    } catch (error) {
      this.logger.error("Failed to subscribe to Salesforce channel", {
        channel,
        label,
        error: error instanceof Error ? error.message : String(error),
      });
      throw error;
    }
  }

  /**
   * Close the Pub/Sub client connection
   */
  async close(): Promise<void> {
    if (!this.client) return;

    try {
      await this.client.close();
      this.logger.debug("Salesforce Pub/Sub client closed");
    } catch (error) {
      this.logger.warn("Failed to close Salesforce Pub/Sub client cleanly", {
        error: error instanceof Error ? error.message : String(error),
      });
    } finally {
      this.client = null;
    }
  }

  private loadPubSubCtor(): PubSubClientConstructor {
    if (this.pubSubCtor) {
      return this.pubSubCtor;
    }

    const maybeCtor = (PubSubApiClientPkg as unknown as PubSubClientConstructor) ?? null;
    const maybeDefault =
      (PubSubApiClientPkg as { default?: PubSubClientConstructor }).default ?? null;
    const ctor = typeof maybeCtor === "function" ? maybeCtor : maybeDefault;

    if (!ctor) {
      throw new Error("Failed to load Salesforce Pub/Sub client constructor");
    }

    this.pubSubCtor = ctor;
    return this.pubSubCtor;
  }
}
