/**
 * Salesforce Order Integration Service
 *
 * Encapsulates all Salesforce Order operations.
 * - Builds SOQL queries
 * - Executes queries via SalesforceConnection
 * - Uses domain mappers for transformation
 * - Returns domain types
 *
 * This is an infrastructure service - application layer (orchestrators)
 * should use this instead of building queries directly.
 */

import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { SalesforceConnection } from "./salesforce-connection.service";
import { assertSalesforceId, buildInClause } from "../utils/soql.util";
import { getErrorMessage } from "@bff/core/utils/error.util";
import {
  Providers as OrderProviders,
  type OrderDetails,
  type OrderSummary,
  type SalesforceOrderRecord,
  type SalesforceOrderItemRecord,
} from "@customer-portal/domain/orders";
import type { SalesforceResponse } from "@customer-portal/domain/common";
import { OrderFieldMapService } from "@bff/modules/orders/config/order-field-map.service";

/**
 * Salesforce Order Service
 *
 * Single point of integration for Order operations with Salesforce.
 * Application layer should use this service instead of direct SF queries.
 */
@Injectable()
export class SalesforceOrderService {
  constructor(
    private readonly sf: SalesforceConnection,
    @Inject(Logger) private readonly logger: Logger,
    private readonly orderFieldMap: OrderFieldMapService
  ) {}

  private readonly compositeOrderReference = "order_ref";

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

    // Build queries
    const orderQueryFields = this.orderFieldMap.buildOrderSelectFields(["Account.Name"]).join(", ");
    const orderItemProduct2Fields = this.orderFieldMap
      .buildOrderItemProduct2Fields()
      .map(f => `PricebookEntry.Product2.${f}`);
    const orderItemSelect = [
      ...this.orderFieldMap.buildOrderItemSelectFields(),
      ...orderItemProduct2Fields,
    ].join(", ");

    const orderSoql = `
      SELECT ${orderQueryFields}
      FROM Order
      WHERE Id = '${safeOrderId}'
      LIMIT 1
    `;

    const orderItemsSoql = `
      SELECT ${orderItemSelect}
      FROM OrderItem
      WHERE OrderId = '${safeOrderId}'
      ORDER BY CreatedDate ASC
    `;

    try {
      // Execute queries in parallel
      const [orderResult, itemsResult] = await Promise.all([
        this.sf.query(orderSoql, { label: "orders:getOrderById" }) as Promise<
          SalesforceResponse<SalesforceOrderRecord>
        >,
        this.sf.query(orderItemsSoql, { label: "orders:getOrderItemsById" }) as Promise<
          SalesforceResponse<SalesforceOrderItemRecord>
        >,
      ]);

      const order = orderResult.records?.[0];

      if (!order) {
        this.logger.warn({ orderId }, "Order not found");
        return null;
      }

      const orderItems = itemsResult.records ?? [];

      this.logger.log(
        { orderId: safeOrderId, itemCount: orderItems.length },
        "Order details retrieved with items"
      );

      // Use domain mapper - single transformation!
      return OrderProviders.Salesforce.transformSalesforceOrderDetails(
        order,
        orderItems,
        this.orderFieldMap.fields
      );
    } catch (error: unknown) {
      this.logger.error("Failed to fetch order with items", {
        error: getErrorMessage(error),
        orderId,
      });
      throw error;
    }
  }

  /**
   * Create a new order in Salesforce
   */
  async createOrder(orderFields: Record<string, unknown>): Promise<{ id: string }> {
    const typeField = this.orderFieldMap.fields.order.type;
    this.logger.log({ orderType: orderFields[typeField] }, "Creating Salesforce Order");

    try {
      const created = (await this.sf.sobject("Order").create(orderFields)) as { id: string };
      this.logger.log({ orderId: created.id }, "Salesforce Order created successfully");
      return created;
    } catch (error: unknown) {
      this.logger.error("Failed to create Salesforce Order", {
        error: getErrorMessage(error),
        orderType: orderFields[typeField],
      });
      throw error;
    }
  }

  async createOrderWithItems(
    orderFields: Record<string, unknown>,
    items: Array<{ pricebookEntryId: string; unitPrice: number; quantity: number; sku?: string }>
  ): Promise<{ id: string }> {
    if (!items.length) {
      this.logger.warn(
        "No order items provided for composite order creation; falling back to single order create"
      );
      return this.createOrder(orderFields);
    }

    const typeField = this.orderFieldMap.fields.order.type;
    const payload = {
      records: [
        {
          attributes: { type: "Order", referenceId: this.compositeOrderReference },
          ...orderFields,
          OrderItems: {
            records: items.map((item, index) => ({
              attributes: { type: "OrderItem", referenceId: `order_item_${index + 1}` },
              PricebookEntryId: item.pricebookEntryId,
              Quantity: item.quantity,
              UnitPrice: item.unitPrice,
            })),
          },
        },
      ],
    } satisfies SalesforceCompositeTreeRequest;

    try {
      const result = await this.sf.compositeTree<SalesforceCompositeTreeResponse>(
        "Order",
        payload,
        { allOrNone: true, priority: 8, label: "orders:createWithItems" }
      );

      if (result.hasErrors) {
        const errorDetails = this.flattenCompositeErrors(result)
          .map(err => `[${err.statusCode}] ${err.message}`)
          .join("; ");

        throw new Error(
          errorDetails || "Salesforce composite tree returned errors during order creation"
        );
      }

      const orderResult = result.results.find(
        entry => entry.referenceId === this.compositeOrderReference
      );

      if (!orderResult?.id) {
        throw new Error("Salesforce composite tree response missing order ID");
      }

      this.logger.log(
        {
          orderId: orderResult.id,
          orderType: orderFields[typeField],
          orderItems: items.length,
        },
        "Salesforce order created via composite tree"
      );

      return { id: orderResult.id };
    } catch (error: unknown) {
      this.logger.error("Failed to create Salesforce order with composite tree", {
        error: getErrorMessage(error),
        orderType: orderFields[typeField],
        orderItems: items.length,
      });
      throw error;
    }
  }

  private flattenCompositeErrors(response: SalesforceCompositeTreeResponse) {
    return (response.results ?? []).flatMap(entry => entry.errors ?? []);
  }

  /**
   * Get orders for a Salesforce account with item summaries
   */
  async getOrdersForAccount(sfAccountId: string): Promise<OrderSummary[]> {
    const safeAccountId = assertSalesforceId(sfAccountId, "sfAccountId");
    this.logger.log({ sfAccountId: safeAccountId }, "Fetching orders for account");

    // Build queries
    const orderQueryFields = this.orderFieldMap.buildOrderSelectFields().join(", ");
    const orderItemProduct2Fields = this.orderFieldMap
      .buildOrderItemProduct2Fields()
      .map(f => `PricebookEntry.Product2.${f}`);
    const orderItemSelect = [
      ...this.orderFieldMap.buildOrderItemSelectFields(),
      ...orderItemProduct2Fields,
    ].join(", ");

    const ordersSoql = `
      SELECT ${orderQueryFields}
      FROM Order
      WHERE AccountId = '${safeAccountId}'
      ORDER BY CreatedDate DESC
      LIMIT 50
    `;

    try {
      // Fetch orders
      const ordersResult = (await this.sf.query(ordersSoql, {
        label: "orders:listByAccount",
      })) as SalesforceResponse<SalesforceOrderRecord>;
      const orders = ordersResult.records || [];

      if (orders.length === 0) {
        return [];
      }

      // Get order items for all orders in one query
      const rawOrderIds = orders
        .map(order => order.Id)
        .filter((id): id is string => typeof id === "string");

      if (rawOrderIds.length === 0) {
        return [];
      }

      const orderIds = rawOrderIds.map(id => assertSalesforceId(id, "orderId"));
      const inClause = buildInClause(orderIds, "orderIds");

      const itemsSoql = `
        SELECT ${orderItemSelect}
        FROM OrderItem
        WHERE OrderId IN ${inClause}
        ORDER BY CreatedDate ASC
      `;

      const itemsResult = (await this.sf.query(itemsSoql, {
        label: "orders:listItemsByAccount",
      })) as SalesforceResponse<SalesforceOrderItemRecord>;
      const allItems = itemsResult.records || [];

      // Group items by order ID
      const itemsByOrder: Record<string, SalesforceOrderItemRecord[]> = {};
      for (const item of allItems) {
        const orderId = item.OrderId;
        if (typeof orderId === "string") {
          if (!itemsByOrder[orderId]) {
            itemsByOrder[orderId] = [];
          }
          itemsByOrder[orderId].push(item);
        }
      }

      this.logger.log(
        {
          sfAccountId: safeAccountId,
          orderCount: orders.length,
          totalItems: allItems.length,
        },
        "Orders retrieved for account with item summaries"
      );

      // Use domain mapper for each order - single transformation!
      return orders
        .filter(
          (order): order is SalesforceOrderRecord & { Id: string } => typeof order.Id === "string"
        )
        .map(order =>
          OrderProviders.Salesforce.transformSalesforceOrderSummary(
            order,
            itemsByOrder[order.Id] ?? [],
            this.orderFieldMap.fields
          )
        );
    } catch (error: unknown) {
      this.logger.error("Failed to fetch orders for account", {
        error: getErrorMessage(error),
        sfAccountId,
      });
      throw error;
    }
  }
}

interface SalesforceCompositeTreeRequest {
  records: Array<{
    attributes: { type: string; referenceId: string };
    OrderItems?: {
      records: Array<{
        attributes: { type: string; referenceId: string };
        PricebookEntryId: string;
        Quantity: number;
        UnitPrice: number;
      }>;
    };
    [key: string]: unknown;
  }>;
}

interface SalesforceCompositeTreeResponse {
  hasErrors: boolean;
  results: Array<{
    referenceId?: string;
    id?: string;
    errors?: Array<{ statusCode: string; message: string; fields?: string[] }>;
  }>;
}
