/**
 * Salesforce SIM Inventory Integration Service
 *
 * Manages Physical SIM inventory records in Salesforce.
 * - Query SIM_Inventory__c by ID
 * - Validate SIM availability status
 * - Update SIM status after activation
 *
 * @see docs/integrations/salesforce/sim-inventory.md
 */

import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { SalesforceConnection } from "./salesforce-connection.service.js";
import { assertSalesforceId } from "../utils/soql.util.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { SimActivationException } from "@bff/core/exceptions/domain-exceptions.js";

/**
 * SIM Inventory Status values in Salesforce
 */
export const SIM_INVENTORY_STATUS = {
  AVAILABLE: "Available",
  ASSIGNED: "Assigned",
  RESERVED: "Reserved",
  DEACTIVATED: "Deactivated",
} as const;

export type SimInventoryStatus = (typeof SIM_INVENTORY_STATUS)[keyof typeof SIM_INVENTORY_STATUS];

/**
 * Optional assignment details when marking a SIM as assigned
 */
export interface SimAssignmentDetails {
  /** Salesforce Account ID to assign the SIM to */
  accountId?: string;
  /** Salesforce Order ID that assigned the SIM */
  orderId?: string;
  /** SIM Type (eSIM or Physical SIM) */
  simType?: string;
}

/**
 * SIM Inventory record from Salesforce
 */
export interface SimInventoryRecord {
  id: string;
  phoneNumber: string;
  ptNumber: string;
  oemId?: string;
  status: SimInventoryStatus;
  // Note: ICCID__c and IMSI__c fields don't exist on SIM_Inventory__c in this Salesforce org
}

/**
 * Raw Salesforce SIM_Inventory__c response
 */
interface SalesforceSIMInventoryResponse {
  records: Array<{
    Id: string;
    Phone_Number__c?: string | null;
    PT_Number__c?: string | null;
    OEM_ID__c?: string | null;
    Status__c?: string | null;
  }>;
}

/** Payload for updating a SIM_Inventory__c record */
interface SimInventoryUpdatePayload {
  Id: string;
  Status__c: SimInventoryStatus;
  Assigned_Account__c?: string;
  Assigned_Order__c?: string;
  SIM_Type__c?: string;
}

@Injectable()
export class SalesforceSIMInventoryService {
  constructor(
    private readonly sf: SalesforceConnection,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * Get SIM Inventory record by ID
   *
   * @param simInventoryId - Salesforce ID of the SIM_Inventory__c record
   * @returns SIM Inventory details or null if not found
   */
  async getSimInventoryById(simInventoryId: string): Promise<SimInventoryRecord | null> {
    const safeId = assertSalesforceId(simInventoryId, "simInventoryId");
    this.logger.log("Fetching SIM Inventory record", { simInventoryId: safeId });

    // Note: ICCID__c and IMSI__c fields don't exist on SIM_Inventory__c in this org
    // Only query fields that actually exist
    const soql = `
      SELECT Id, Phone_Number__c, PT_Number__c, OEM_ID__c, Status__c
      FROM SIM_Inventory__c
      WHERE Id = '${safeId}'
      LIMIT 1
    `;

    try {
      const result = (await this.sf.query(soql, {
        label: "sim-inventory:getById",
      })) as SalesforceSIMInventoryResponse;

      const record = result.records?.[0];

      if (!record) {
        this.logger.warn("SIM Inventory record not found", { simInventoryId: safeId });
        return null;
      }

      this.logger.log("SIM Inventory record retrieved", {
        simInventoryId: safeId,
        status: record.Status__c,
        hasPhoneNumber: !!record.Phone_Number__c,
        hasPtNumber: !!record.PT_Number__c,
      });

      return this.mapToSimInventoryRecord(record);
    } catch (error: unknown) {
      this.logger.error("Failed to fetch SIM Inventory record", {
        simInventoryId: safeId,
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Get and validate SIM Inventory for activation
   *
   * Fetches the SIM Inventory record and validates:
   * - Record exists
   * - Status is "Available"
   * - PT_Number__c (productNumber) is set
   * - Phone_Number__c (MSISDN) is set
   *
   * @throws SimActivationException if validation fails
   */
  async getAndValidateForActivation(simInventoryId: string): Promise<SimInventoryRecord> {
    const record = await this.getSimInventoryById(simInventoryId);

    if (!record) {
      throw new SimActivationException("SIM Inventory record not found", {
        simInventoryId,
      });
    }

    // Validate status is "Available"
    if (record.status !== SIM_INVENTORY_STATUS.AVAILABLE) {
      throw new SimActivationException(
        `SIM is not available for activation. Current status: ${record.status}`,
        {
          simInventoryId,
          currentStatus: record.status,
          expectedStatus: SIM_INVENTORY_STATUS.AVAILABLE,
        }
      );
    }

    // Validate PT Number is set
    if (!record.ptNumber) {
      throw new SimActivationException("SIM Inventory record missing PT Number", {
        simInventoryId,
      });
    }

    // Validate Phone Number is set
    if (!record.phoneNumber) {
      throw new SimActivationException("SIM Inventory record missing Phone Number (MSISDN)", {
        simInventoryId,
      });
    }

    this.logger.log("SIM Inventory validated for activation", {
      simInventoryId,
      phoneNumber: record.phoneNumber,
      ptNumber: record.ptNumber,
    });

    return record;
  }

  /**
   * Update SIM Inventory status to "Assigned" after successful activation
   *
   * @param simInventoryId - Salesforce ID of the SIM_Inventory__c record
   * @param details - Optional assignment details (account, order, SIM type)
   */
  async markAsAssigned(simInventoryId: string, details?: SimAssignmentDetails): Promise<void> {
    const safeId = assertSalesforceId(simInventoryId, "simInventoryId");

    this.logger.log("Marking SIM Inventory as Assigned", {
      simInventoryId: safeId,
      hasAssignmentDetails: !!details,
      accountId: details?.accountId,
      orderId: details?.orderId,
      simType: details?.simType,
    });

    try {
      const updatePayload: SimInventoryUpdatePayload = {
        Id: safeId,
        Status__c: SIM_INVENTORY_STATUS.ASSIGNED,
      };

      // Add optional assignment fields if provided
      if (details?.accountId) {
        updatePayload.Assigned_Account__c = details.accountId;
      }
      if (details?.orderId) {
        updatePayload.Assigned_Order__c = details.orderId;
      }
      if (details?.simType) {
        updatePayload.SIM_Type__c = details.simType;
      }

      await this.sf.sobject("SIM_Inventory__c").update?.(updatePayload as { Id: string });

      this.logger.log("SIM Inventory marked as Assigned", {
        simInventoryId: safeId,
        assignedAccount: details?.accountId,
        assignedOrder: details?.orderId,
        simType: details?.simType,
      });
    } catch (error: unknown) {
      this.logger.error("Failed to update SIM Inventory status", {
        simInventoryId: safeId,
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Update SIM Inventory status
   *
   * @param simInventoryId - Salesforce ID
   * @param status - New status value
   */
  async updateStatus(simInventoryId: string, status: SimInventoryStatus): Promise<void> {
    const safeId = assertSalesforceId(simInventoryId, "simInventoryId");

    this.logger.log("Updating SIM Inventory status", {
      simInventoryId: safeId,
      newStatus: status,
    });

    try {
      await this.sf.sobject("SIM_Inventory__c").update?.({
        Id: safeId,
        Status__c: status,
      });

      this.logger.log("SIM Inventory status updated", {
        simInventoryId: safeId,
        newStatus: status,
      });
    } catch (error: unknown) {
      this.logger.error("Failed to update SIM Inventory status", {
        simInventoryId: safeId,
        newStatus: status,
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Map raw Salesforce record to domain type
   */
  private mapToSimInventoryRecord(
    raw: SalesforceSIMInventoryResponse["records"][0]
  ): SimInventoryRecord {
    return {
      id: raw.Id,
      phoneNumber: raw.Phone_Number__c ?? "",
      ptNumber: raw.PT_Number__c ?? "",
      ...(raw.OEM_ID__c && { oemId: raw.OEM_ID__c }),
      status: (raw.Status__c as SimInventoryStatus) ?? SIM_INVENTORY_STATUS.AVAILABLE,
    };
  }
}
