import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { OrderEventsService } from "./order-events.service.js";
import { OrdersCacheService } from "./orders-cache.service.js";
import type { OrderUpdateEventPayload } from "@customer-portal/domain/orders";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
import { NotificationService } from "@bff/modules/notifications/notifications.service.js";
import { NOTIFICATION_SOURCE, NOTIFICATION_TYPE } from "@customer-portal/domain/notifications";
import { salesforceAccountIdSchema } from "@customer-portal/domain/common";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";

/**
 * Fulfillment Side Effects Service
 *
 * Handles all non-critical side effects during order fulfillment:
 * - Event publishing (SSE/WebSocket notifications)
 * - In-app notification creation
 * - Cache invalidation
 *
 * All methods are safe and do not throw - they log warnings on failure.
 * This keeps the main orchestration flow clean and focused on critical operations.
 */
@Injectable()
export class FulfillmentSideEffectsService {
  constructor(
    @Inject(Logger) private readonly logger: Logger,
    private readonly orderEvents: OrderEventsService,
    private readonly ordersCache: OrdersCacheService,
    private readonly mappingsService: MappingsService,
    private readonly notifications: NotificationService
  ) {}

  /**
   * Publish a status update event for real-time UI updates
   */
  publishStatusUpdate(sfOrderId: string, event: Omit<OrderUpdateEventPayload, "orderId">): void {
    this.orderEvents.publish(sfOrderId, {
      orderId: sfOrderId,
      ...event,
    });
  }

  /**
   * Publish event for order starting activation
   */
  publishActivating(sfOrderId: string): void {
    this.publishStatusUpdate(sfOrderId, {
      status: "Processing",
      activationStatus: "Activating",
      stage: "in_progress",
      source: "fulfillment",
      timestamp: new Date().toISOString(),
    });
  }

  /**
   * Publish event for order completed successfully
   */
  publishCompleted(sfOrderId: string, whmcsOrderId?: number, whmcsServiceIds?: number[]): void {
    this.publishStatusUpdate(sfOrderId, {
      status: "Completed",
      activationStatus: "Activated",
      stage: "completed",
      source: "fulfillment",
      timestamp: new Date().toISOString(),
      payload: {
        ...(whmcsOrderId !== undefined && { whmcsOrderId }),
        ...(whmcsServiceIds !== undefined && { whmcsServiceIds }),
      },
    });
  }

  /**
   * Publish event for order already provisioned (idempotent case)
   */
  publishAlreadyProvisioned(sfOrderId: string, whmcsOrderId?: string): void {
    this.publishStatusUpdate(sfOrderId, {
      status: "Completed",
      activationStatus: "Activated",
      stage: "completed",
      source: "fulfillment",
      message: "Order already provisioned",
      timestamp: new Date().toISOString(),
      payload: {
        ...(whmcsOrderId !== undefined && { whmcsOrderId }),
      },
    });
  }

  /**
   * Publish event for order fulfillment failed
   */
  publishFailed(sfOrderId: string, reason: string): void {
    this.publishStatusUpdate(sfOrderId, {
      status: "Pending Review",
      activationStatus: "Failed",
      stage: "failed",
      source: "fulfillment",
      timestamp: new Date().toISOString(),
      reason,
    });
  }

  /**
   * Create in-app notification for order approved (started processing)
   */
  async notifyOrderApproved(sfOrderId: string, accountId?: unknown): Promise<void> {
    await this.safeCreateNotification({
      type: NOTIFICATION_TYPE.ORDER_APPROVED,
      sfOrderId,
      accountId,
      actionUrl: `/account/orders/${sfOrderId}`,
    });
  }

  /**
   * Create in-app notification for order activated (completed)
   */
  async notifyOrderActivated(sfOrderId: string, accountId?: unknown): Promise<void> {
    await this.safeCreateNotification({
      type: NOTIFICATION_TYPE.ORDER_ACTIVATED,
      sfOrderId,
      accountId,
      actionUrl: "/account/services",
    });
  }

  /**
   * Create in-app notification for order failed
   */
  async notifyOrderFailed(sfOrderId: string, accountId?: unknown): Promise<void> {
    await this.safeCreateNotification({
      type: NOTIFICATION_TYPE.ORDER_FAILED,
      sfOrderId,
      accountId,
      actionUrl: `/account/orders/${sfOrderId}`,
    });
  }

  /**
   * Invalidate caches for order data
   */
  async invalidateCaches(sfOrderId: string, accountId?: string | null): Promise<void> {
    const tasks: Array<Promise<unknown>> = [this.ordersCache.invalidateOrder(sfOrderId)];

    if (accountId) {
      tasks.push(this.ordersCache.invalidateAccountOrders(accountId));
    }

    try {
      await Promise.all(tasks);
    } catch (error) {
      this.logger.warn("Failed to invalidate order caches", {
        sfOrderId,
        accountId: accountId ?? undefined,
        error: extractErrorMessage(error),
      });
    }
  }

  /**
   * Execute all completion side effects
   */
  async onFulfillmentComplete(
    sfOrderId: string,
    accountId?: string | null,
    whmcsOrderId?: number,
    whmcsServiceIds?: number[]
  ): Promise<void> {
    this.publishCompleted(sfOrderId, whmcsOrderId, whmcsServiceIds);
    await this.notifyOrderActivated(sfOrderId, accountId);
    await this.invalidateCaches(sfOrderId, accountId);
  }

  /**
   * Execute all failure side effects
   */
  async onFulfillmentFailed(
    sfOrderId: string,
    accountId?: string | null,
    reason: string = "Fulfillment failed"
  ): Promise<void> {
    this.publishFailed(sfOrderId, reason);
    await this.notifyOrderFailed(sfOrderId, accountId);
    await this.invalidateCaches(sfOrderId, accountId);
  }

  /**
   * Safe notification creation - never throws
   */
  private async safeCreateNotification(params: {
    type: (typeof NOTIFICATION_TYPE)[keyof typeof NOTIFICATION_TYPE];
    sfOrderId: string;
    accountId?: unknown;
    actionUrl: string;
  }): Promise<void> {
    try {
      const sfAccountId = salesforceAccountIdSchema.safeParse(params.accountId);
      if (!sfAccountId.success) return;

      const mapping = await this.mappingsService.findBySfAccountId(sfAccountId.data);
      if (!mapping?.userId) return;

      await this.notifications.createNotification({
        userId: mapping.userId,
        type: params.type,
        source: NOTIFICATION_SOURCE.SYSTEM,
        sourceId: params.sfOrderId,
        actionUrl: params.actionUrl,
      });
    } catch (error) {
      this.logger.warn(
        {
          sfOrderId: params.sfOrderId,
          type: params.type,
          err: error instanceof Error ? error.message : String(error),
        },
        "Failed to create in-app order notification"
      );
    }
  }
}
