import { Injectable, Inject, NotFoundException } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { SalesforceOrderService } from "@bff/integrations/salesforce/services/salesforce-order.service.js";
import { SalesforceOpportunityService } from "@bff/integrations/salesforce/services/salesforce-opportunity.service.js";
import { OpportunityResolutionService } from "@bff/integrations/salesforce/services/opportunity-resolution.service.js";
import { OrderValidator } from "./order-validator.service.js";
import { OrderBuilder } from "./order-builder.service.js";
import { OrderItemBuilder } from "./order-item-builder.service.js";
import type { OrderItemCompositePayload } from "./order-item-builder.service.js";
import { OrdersCacheService } from "./orders-cache.service.js";
import { WorkflowCaseManager } from "@bff/modules/shared/workflow/index.js";
import { CacheCoordinatorService } from "@bff/infra/cache/cache.module.js";
import type {
  OrderDetails,
  OrderSummary,
  OrderTypeValue,
  CreateOrderRequest,
} from "@customer-portal/domain/orders";
import { assertSalesforceId } from "@bff/integrations/salesforce/utils/soql.util.js";
import { OPPORTUNITY_STAGE } from "@customer-portal/domain/opportunity";

const SF_ACCOUNT_ID_FIELD = "sfAccountId";
const ORDER_NOT_FOUND = "Order not found";

type OrderDetailsResponse = OrderDetails;
type OrderSummaryResponse = OrderSummary;

/**
 * Main orchestrator for order operations
 * Coordinates all order-related services
 */
@Injectable()
export class OrderOrchestrator {
  constructor(
    @Inject(Logger) private readonly logger: Logger,
    private readonly salesforceOrderService: SalesforceOrderService,
    private readonly opportunityService: SalesforceOpportunityService,
    private readonly opportunityResolution: OpportunityResolutionService,
    private readonly orderValidator: OrderValidator,
    private readonly orderBuilder: OrderBuilder,
    private readonly orderItemBuilder: OrderItemBuilder,
    private readonly ordersCache: OrdersCacheService,
    private readonly workflowCases: WorkflowCaseManager,
    private readonly cacheCoordinator: CacheCoordinatorService
  ) {}

  /**
   * Create a new order - main entry point
   */
  async createOrder(userId: string, body: CreateOrderRequest) {
    this.logger.log({ userId }, "Order creation workflow started");

    // 1) Complete validation (format + business rules)
    const { validatedBody, userMapping, pricebookId } =
      await this.orderValidator.validateCompleteOrder(userId, body);

    this.logger.log(
      {
        userId,
        orderType: validatedBody.orderType,
        skuCount: validatedBody.skus.length,
      },
      "Order validation completed successfully"
    );

    // 2) Resolve Opportunity for this order
    const { opportunityId, wasCreated: opportunityCreated } = await this.resolveOpportunityForOrder(
      validatedBody.orderType,
      userMapping.sfAccountId,
      validatedBody.opportunityId
    );

    // 3) Build order fields with Opportunity link
    const bodyWithOpportunity = opportunityId ? { ...validatedBody, opportunityId } : validatedBody;

    const orderFields = await this.orderBuilder.buildOrderFields(
      bodyWithOpportunity,
      userMapping,
      pricebookId,
      validatedBody.userId
    );

    const orderItemsPayload: OrderItemCompositePayload[] =
      await this.orderItemBuilder.buildOrderItemsPayload(validatedBody.skus, pricebookId);

    this.logger.log(
      {
        userId,
        orderType: validatedBody.orderType,
        skuCount: validatedBody.skus.length,
        orderItemCount: orderItemsPayload.length,
        hasOpportunity: !!opportunityId,
      },
      "Order payload prepared"
    );

    const created = await this.salesforceOrderService.createOrderWithItems(
      orderFields,
      orderItemsPayload
    );

    // 4) Update Opportunity stage to Post Processing
    if (opportunityId) {
      try {
        await this.opportunityService.updateStage(
          opportunityId,
          OPPORTUNITY_STAGE.POST_PROCESSING,
          "Order placed via Portal"
        );
        this.logger.log("Opportunity stage updated to Post Processing", {
          opportunityIdTail: opportunityId.slice(-4),
          orderId: created.id,
        });
      } catch {
        // Log but don't fail the order
        this.logger.warn("Failed to update Opportunity stage after order", {
          opportunityId,
          orderId: created.id,
        });
      }
    }

    // 5) Create internal "Order Placed" case for CS team
    if (userMapping.sfAccountId) {
      await this.workflowCases.notifyOrderPlaced({
        accountId: userMapping.sfAccountId,
        orderId: created.id,
        orderType: validatedBody.orderType,
        ...(opportunityId ? { opportunityId } : {}),
        opportunityCreated,
      });
    }

    // 6) Invalidate caches
    await this.cacheCoordinator.invalidateMultiple([
      { name: "order", execute: async () => this.ordersCache.invalidateOrder(created.id) },
      ...(userMapping.sfAccountId
        ? [
            {
              name: "accountOrders",
              execute: async () =>
                this.ordersCache.invalidateAccountOrders(userMapping.sfAccountId),
            },
          ]
        : []),
    ]);

    this.logger.log(
      {
        orderId: created.id,
        skuCount: validatedBody.skus.length,
        orderItemCount: orderItemsPayload.length,
        opportunityId,
        opportunityCreated,
      },
      "Order creation workflow completed successfully"
    );

    return {
      sfOrderId: created.id,
      status: "Created",
      message: "Order created successfully in Salesforce",
    };
  }

  /**
   * Resolve Opportunity for an order
   *
   * - If order already has an Opportunity ID, use it
   * - Otherwise, find existing open Opportunity for this product type
   * - If none found, create a new one with Post Processing stage
   */
  private async resolveOpportunityForOrder(
    orderType: OrderTypeValue,
    sfAccountId: string | null,
    existingOpportunityId?: string
  ): Promise<{ opportunityId: string | null; wasCreated: boolean }> {
    try {
      const result = await this.opportunityResolution.resolveForOrderPlacement({
        accountId: sfAccountId,
        orderType,
        existingOpportunityId,
      });
      if (result.opportunityId) {
        this.logger.debug("Resolved Opportunity for order", {
          opportunityIdTail: result.opportunityId.slice(-4),
          wasCreated: result.wasCreated,
          orderType,
        });
      }
      return result;
    } catch {
      const accountIdTail =
        typeof sfAccountId === "string" && sfAccountId.length >= 4 ? sfAccountId.slice(-4) : "none";
      this.logger.warn("Failed to resolve Opportunity for order", { orderType, accountIdTail });
      // Don't fail the order if Opportunity resolution fails
      return { opportunityId: null, wasCreated: false };
    }
  }

  /**
   * Get order by ID with order items
   */
  async getOrder(orderId: string): Promise<OrderDetailsResponse | null> {
    const safeOrderId = assertSalesforceId(orderId, "orderId");
    this.logger.log({ orderId: safeOrderId }, "Fetching order details");

    // Use integration service - it handles queries and transformations
    return this.ordersCache.getOrderDetails(safeOrderId, async () =>
      this.salesforceOrderService.getOrderById(safeOrderId)
    );
  }

  /**
   * Get order scoped to authenticated user account
   */
  async getOrderForUser(orderId: string, userId: string): Promise<OrderDetailsResponse> {
    const userMapping = await this.orderValidator.validateUserMapping(userId);
    const sfAccountId = userMapping.sfAccountId
      ? assertSalesforceId(userMapping.sfAccountId, SF_ACCOUNT_ID_FIELD)
      : null;

    if (!sfAccountId) {
      this.logger.warn({ userId }, "User mapping missing Salesforce account ID");
      throw new NotFoundException(ORDER_NOT_FOUND);
    }

    const safeOrderId = assertSalesforceId(orderId, "orderId");
    const order = await this.getOrder(safeOrderId);
    if (!order) {
      throw new NotFoundException(ORDER_NOT_FOUND);
    }

    if (!order.accountId || order.accountId !== sfAccountId) {
      this.logger.warn(
        {
          userId,
          orderId: safeOrderId,
          orderAccountId: order.accountId,
          expectedAccountId: sfAccountId,
        },
        "Order access denied due to account mismatch"
      );
      throw new NotFoundException(ORDER_NOT_FOUND);
    }

    return order;
  }

  /**
   * Get orders for a user with basic item summary
   */
  async getOrdersForUser(userId: string): Promise<OrderSummaryResponse[]> {
    this.logger.log({ userId }, "Fetching user orders");

    // Get user mapping
    const userMapping = await this.orderValidator.validateUserMapping(userId);
    const sfAccountId = userMapping.sfAccountId
      ? assertSalesforceId(userMapping.sfAccountId, SF_ACCOUNT_ID_FIELD)
      : undefined;

    if (!sfAccountId) {
      this.logger.warn({ userId }, "User mapping missing Salesforce account ID");
      return [];
    }

    // Use integration service - it handles queries and transformations
    return this.ordersCache.getOrderSummaries(sfAccountId, async () =>
      this.salesforceOrderService.getOrdersForAccount(sfAccountId)
    );
  }
}
