/**
 * Opportunity Mutation Service
 *
 * Handles create, update, and link operations for Salesforce Opportunities.
 * Extracted from SalesforceOpportunityService for single-responsibility.
 */

import { Injectable, Inject } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
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 {
  type OpportunityStageValue,
  type OpportunityProductTypeValue,
  type CreateOpportunityRequest,
  OPPORTUNITY_STAGE,
  APPLICATION_STAGE,
  OPPORTUNITY_PRODUCT_TYPE,
  getDefaultCommodityType,
} from "@customer-portal/domain/opportunity";
import { OPPORTUNITY_FIELD_MAP } from "../../constants/index.js";

@Injectable()
export class OpportunityMutationService {
  private readonly opportunityRecordTypeIds: Partial<
    Record<OpportunityProductTypeValue, string | undefined>
  >;

  constructor(
    private readonly sf: SalesforceConnection,
    private readonly configService: ConfigService,
    @Inject(Logger) private readonly logger: Logger
  ) {
    this.opportunityRecordTypeIds = {
      [OPPORTUNITY_PRODUCT_TYPE.INTERNET]: this.configService.get<string>(
        "OPPORTUNITY_RECORD_TYPE_ID_INTERNET"
      ),
      [OPPORTUNITY_PRODUCT_TYPE.SIM]: this.configService.get<string>(
        "OPPORTUNITY_RECORD_TYPE_ID_SIM"
      ),
      [OPPORTUNITY_PRODUCT_TYPE.VPN]: this.configService.get<string>(
        "OPPORTUNITY_RECORD_TYPE_ID_VPN"
      ),
    };
  }

  /**
   * Create a new Opportunity in Salesforce
   */
  async createOpportunity(request: CreateOpportunityRequest): Promise<string> {
    const safeAccountId = assertSalesforceId(request.accountId, "accountId");

    this.logger.log("Creating Opportunity for service lifecycle tracking", {
      accountId: safeAccountId,
      productType: request.productType,
      stage: request.stage,
      source: request.source,
    });

    const opportunityName = `Portal - ${request.productType}`;
    const closeDate =
      request.closeDate ?? this.calculateCloseDate(request.productType, request.stage);
    const applicationStage = request.applicationStage ?? APPLICATION_STAGE.INTRO_1;
    const commodityType = getDefaultCommodityType(request.productType);

    const recordTypeId = this.resolveOpportunityRecordTypeId(request.productType);
    const payload: Record<string, unknown> = {
      [OPPORTUNITY_FIELD_MAP.name]: opportunityName,
      [OPPORTUNITY_FIELD_MAP.accountId]: safeAccountId,
      [OPPORTUNITY_FIELD_MAP.stage]: request.stage,
      [OPPORTUNITY_FIELD_MAP.closeDate]: closeDate,
      [OPPORTUNITY_FIELD_MAP.applicationStage]: applicationStage,
      [OPPORTUNITY_FIELD_MAP.commodityType]: commodityType,
      ...(recordTypeId ? { RecordTypeId: recordTypeId } : {}),
    };

    if (request.source) {
      payload[OPPORTUNITY_FIELD_MAP.source] = request.source;
    }

    try {
      const createMethod = this.sf.sobject("Opportunity").create;
      if (!createMethod) {
        throw new Error("Salesforce Opportunity create method not available");
      }

      const result = (await createMethod(payload)) as { id?: string; success?: boolean };

      if (!result?.id) {
        throw new Error("Salesforce did not return Opportunity ID");
      }

      this.logger.log("Opportunity created successfully", {
        opportunityId: result.id,
        productType: request.productType,
        stage: request.stage,
      });

      return result.id;
    } catch (error) {
      const errorDetails: Record<string, unknown> = {
        error: extractErrorMessage(error),
        accountId: safeAccountId,
        productType: request.productType,
        stage: request.stage,
        payloadFields: Object.keys(payload),
      };

      if (error && typeof error === "object") {
        const err = error as Record<string, unknown>;
        if (err["errorCode"]) errorDetails["errorCode"] = err["errorCode"];
        if (err["fields"]) errorDetails["fields"] = err["fields"];
        if (err["message"]) errorDetails["rawMessage"] = err["message"];
      }

      this.logger.error(errorDetails, "Failed to create Opportunity");
      throw new Error("Failed to create service lifecycle record");
    }
  }

  /**
   * Update Opportunity stage
   */
  async updateStage(
    opportunityId: string,
    stage: OpportunityStageValue,
    reason?: string
  ): Promise<void> {
    const safeOppId = assertSalesforceId(opportunityId, "opportunityId");

    this.logger.log("Updating Opportunity stage", {
      opportunityId: safeOppId,
      newStage: stage,
      reason,
    });

    const payload: Record<string, unknown> = {
      Id: safeOppId,
      [OPPORTUNITY_FIELD_MAP.stage]: stage,
    };

    try {
      const updateMethod = this.sf.sobject("Opportunity").update;
      if (!updateMethod) {
        throw new Error("Salesforce Opportunity update method not available");
      }

      await updateMethod(payload as Record<string, unknown> & { Id: string });

      this.logger.log("Opportunity stage updated successfully", {
        opportunityId: safeOppId,
        stage,
      });
    } catch (error) {
      this.logger.error("Failed to update Opportunity stage", {
        error: extractErrorMessage(error),
        opportunityId: safeOppId,
        stage,
      });
      throw new Error("Failed to update service lifecycle stage");
    }
  }

  /**
   * Link a WHMCS Service ID to an Opportunity
   */
  async linkWhmcsServiceToOpportunity(
    opportunityId: string,
    whmcsServiceId: number
  ): Promise<void> {
    const safeOppId = assertSalesforceId(opportunityId, "opportunityId");

    this.logger.log("Linking WHMCS Service to Opportunity", {
      opportunityId: safeOppId,
      whmcsServiceId,
    });

    const payload: Record<string, unknown> = {
      Id: safeOppId,
      [OPPORTUNITY_FIELD_MAP.whmcsServiceId]: whmcsServiceId,
    };

    try {
      const updateMethod = this.sf.sobject("Opportunity").update;
      if (!updateMethod) {
        throw new Error("Salesforce Opportunity update method not available");
      }

      await updateMethod(payload as Record<string, unknown> & { Id: string });

      this.logger.log("WHMCS Service linked to Opportunity", {
        opportunityId: safeOppId,
        whmcsServiceId,
      });
    } catch (error) {
      this.logger.error("Failed to link WHMCS Service to Opportunity", {
        error: extractErrorMessage(error),
        opportunityId: safeOppId,
        whmcsServiceId,
      });
      // Don't throw - this is a non-critical update
    }
  }

  /**
   * Link an Order to an Opportunity (update Order.OpportunityId)
   */
  async linkOrderToOpportunity(orderId: string, opportunityId: string): Promise<void> {
    const safeOrderId = assertSalesforceId(orderId, "orderId");
    const safeOppId = assertSalesforceId(opportunityId, "opportunityId");

    this.logger.log("Linking Order to Opportunity", {
      orderId: safeOrderId,
      opportunityId: safeOppId,
    });

    try {
      const updateMethod = this.sf.sobject("Order").update;
      if (!updateMethod) {
        throw new Error("Salesforce Order update method not available");
      }

      await updateMethod({
        Id: safeOrderId,
        OpportunityId: safeOppId,
      });

      this.logger.log("Order linked to Opportunity", {
        orderId: safeOrderId,
        opportunityId: safeOppId,
      });
    } catch (error) {
      this.logger.error("Failed to link Order to Opportunity", {
        error: extractErrorMessage(error),
        orderId: safeOrderId,
        opportunityId: safeOppId,
      });
      // Don't throw - this is a non-critical update
    }
  }

  // ==========================================================================
  // Private Helpers
  // ==========================================================================

  private calculateCloseDate(
    _productType: OpportunityProductTypeValue,
    stage: OpportunityStageValue
  ): string {
    const today = new Date();
    let daysToAdd: number;

    switch (stage) {
      case OPPORTUNITY_STAGE.INTRODUCTION:
        daysToAdd = 30;
        break;
      case OPPORTUNITY_STAGE.READY:
        daysToAdd = 14;
        break;
      case OPPORTUNITY_STAGE.POST_PROCESSING:
        daysToAdd = 7;
        break;
      default:
        daysToAdd = 30;
    }

    const closeDate = new Date(today);
    closeDate.setDate(closeDate.getDate() + daysToAdd);

    return closeDate.toISOString().slice(0, 10);
  }

  private resolveOpportunityRecordTypeId(
    productType: OpportunityProductTypeValue
  ): string | undefined {
    const recordTypeId = this.opportunityRecordTypeIds[productType];
    return recordTypeId ? assertSalesforceId(recordTypeId, "opportunityRecordTypeId") : undefined;
  }
}
