/**
 * Opportunity Cancellation Service
 *
 * Handles cancellation-specific operations for Salesforce Opportunities.
 * Extracted from SalesforceOpportunityService for single-responsibility.
 */

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 { SalesforceOperationException } from "@bff/core/exceptions/domain-exceptions.js";
import { OPPORTUNITY_STAGE } from "@customer-portal/domain/opportunity";
import { OPPORTUNITY_FIELD_MAP } from "../../constants/index.js";
import {
  type InternetCancellationOpportunityDataInput,
  type SimCancellationOpportunityDataInput,
  isRecord,
  requireStringField,
} from "./opportunity.types.js";

/**
 * Salesforce Opportunity record payload.
 * Keys are dynamic (resolved from OPPORTUNITY_FIELD_MAP at runtime),
 * so a static interface is not possible.
 */
type SalesforceOpportunityPayload = Record<string, unknown>;

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

  /**
   * Update Opportunity with Internet cancellation data from form submission
   *
   * Sets:
   * - Stage to △Cancelling
   * - ScheduledCancellationDateAndTime__c
   * - CancellationNotice__c to 有 (received)
   * - LineReturn__c to NotYet
   */
  async updateInternetCancellationData(
    opportunityId: string,
    data: InternetCancellationOpportunityDataInput
  ): Promise<void> {
    const safeOppId = assertSalesforceId(opportunityId, "opportunityId");

    const safeData = (() => {
      const unknownData: unknown = data;
      if (!isRecord(unknownData))
        throw new SalesforceOperationException("Invalid cancellation data");

      return {
        scheduledCancellationDate: requireStringField(unknownData, "scheduledCancellationDate"),
        cancellationNotice: requireStringField(unknownData, "cancellationNotice"),
        lineReturnStatus: requireStringField(unknownData, "lineReturnStatus"),
      };
    })();

    this.logger.log("Updating Opportunity with Internet cancellation data", {
      opportunityId: safeOppId,
      scheduledDate: safeData.scheduledCancellationDate,
      cancellationNotice: safeData.cancellationNotice,
    });

    const payload: SalesforceOpportunityPayload = {
      Id: safeOppId,
      [OPPORTUNITY_FIELD_MAP.stage]: OPPORTUNITY_STAGE.CANCELLING,
      [OPPORTUNITY_FIELD_MAP.scheduledCancellationDate]: safeData.scheduledCancellationDate,
      [OPPORTUNITY_FIELD_MAP.cancellationNotice]: safeData.cancellationNotice,
      [OPPORTUNITY_FIELD_MAP.lineReturnStatus]: safeData.lineReturnStatus,
    };

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

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

      this.logger.log("Opportunity Internet cancellation data updated successfully", {
        opportunityId: safeOppId,
        scheduledDate: safeData.scheduledCancellationDate,
      });
    } catch (error) {
      this.logger.error("Failed to update Opportunity Internet cancellation data", {
        error: extractErrorMessage(error),
        opportunityId: safeOppId,
      });
      throw new SalesforceOperationException("Failed to update cancellation information");
    }
  }

  /**
   * Update Opportunity with SIM cancellation data from form submission
   *
   * Sets:
   * - Stage to △Cancelling
   * - SIMScheduledCancellationDateAndTime__c
   * - SIMCancellationNotice__c to 有 (received)
   */
  async updateSimCancellationData(
    opportunityId: string,
    data: SimCancellationOpportunityDataInput
  ): Promise<void> {
    const safeOppId = assertSalesforceId(opportunityId, "opportunityId");

    const safeData = (() => {
      const unknownData: unknown = data;
      if (!isRecord(unknownData))
        throw new SalesforceOperationException("Invalid SIM cancellation data");

      return {
        scheduledCancellationDate: requireStringField(unknownData, "scheduledCancellationDate"),
        cancellationNotice: requireStringField(unknownData, "cancellationNotice"),
      };
    })();

    this.logger.log("Updating Opportunity with SIM cancellation data", {
      opportunityId: safeOppId,
      scheduledDate: safeData.scheduledCancellationDate,
      cancellationNotice: safeData.cancellationNotice,
    });

    const payload: SalesforceOpportunityPayload = {
      Id: safeOppId,
      [OPPORTUNITY_FIELD_MAP.stage]: OPPORTUNITY_STAGE.CANCELLING,
      [OPPORTUNITY_FIELD_MAP.simScheduledCancellationDate]: safeData.scheduledCancellationDate,
      [OPPORTUNITY_FIELD_MAP.simCancellationNotice]: safeData.cancellationNotice,
    };

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

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

      this.logger.log("Opportunity SIM cancellation data updated successfully", {
        opportunityId: safeOppId,
        scheduledDate: safeData.scheduledCancellationDate,
      });
    } catch (error) {
      this.logger.error("Failed to update Opportunity SIM cancellation data", {
        error: extractErrorMessage(error),
        opportunityId: safeOppId,
      });
      throw new SalesforceOperationException("Failed to update SIM cancellation information");
    }
  }

  /**
   * Mark cancellation as complete by updating stage to Cancelled
   */
  async markCancellationComplete(opportunityId: string): Promise<void> {
    const safeOppId = assertSalesforceId(opportunityId, "opportunityId");

    this.logger.log("Marking Opportunity cancellation complete", {
      opportunityId: safeOppId,
    });

    const payload: SalesforceOpportunityPayload = {
      Id: safeOppId,
      [OPPORTUNITY_FIELD_MAP.stage]: OPPORTUNITY_STAGE.CANCELLED,
    };

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

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

      this.logger.log("Opportunity cancellation marked complete", {
        opportunityId: safeOppId,
      });
    } catch (error) {
      this.logger.error("Failed to mark Opportunity cancellation complete", {
        error: extractErrorMessage(error),
        opportunityId: safeOppId,
      });
      throw new SalesforceOperationException("Failed to mark cancellation complete");
    }
  }
}
