import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { DistributedLockService } from "@bff/infra/cache/distributed-lock.service.js";
import { SalesforceOpportunityService } from "./salesforce-opportunity.service.js";
import { assertSalesforceId } from "../utils/soql.util.js";
import { ORDER_TYPE, type OrderTypeValue } from "@customer-portal/domain/orders";
import {
  APPLICATION_STAGE,
  OPPORTUNITY_PRODUCT_TYPE,
  OPPORTUNITY_SOURCE,
  OPPORTUNITY_STAGE,
  OPPORTUNITY_MATCH_STAGES_INTERNET_ELIGIBILITY,
  OPPORTUNITY_MATCH_STAGES_ORDER_PLACEMENT,
  type OpportunityProductTypeValue,
} from "@customer-portal/domain/opportunity";

/**
 * Opportunity Resolution Service
 *
 * Centralizes the "find or create" rules for Opportunities so eligibility, checkout,
 * and other flows cannot drift over time.
 *
 * Key principle:
 * - Eligibility can only match the initial Introduction opportunity.
 * - Order placement can match Introduction/Ready. It must never match Active.
 */
@Injectable()
export class OpportunityResolutionService {
  constructor(
    private readonly opportunities: SalesforceOpportunityService,
    private readonly lockService: DistributedLockService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * Resolve (find or create) an Internet Opportunity for eligibility request.
   *
   * NOTE: The eligibility flow itself should ensure idempotency for Case creation.
   * This method only resolves the Opportunity link.
   */
  async findOrCreateForInternetEligibility(accountId: string): Promise<{
    opportunityId: string;
    wasCreated: boolean;
  }> {
    const safeAccountId = assertSalesforceId(accountId, "accountId");
    const lockKey = `opportunity:eligibility:${safeAccountId}:Internet`;

    return this.lockService.withLock(
      lockKey,
      async () => {
        const existing = await this.opportunities.findOpenOpportunityForAccount(
          safeAccountId,
          OPPORTUNITY_PRODUCT_TYPE.INTERNET,
          { stages: OPPORTUNITY_MATCH_STAGES_INTERNET_ELIGIBILITY }
        );

        if (existing) {
          return { opportunityId: existing, wasCreated: false };
        }

        const created = await this.opportunities.createOpportunity({
          accountId: safeAccountId,
          productType: OPPORTUNITY_PRODUCT_TYPE.INTERNET,
          stage: OPPORTUNITY_STAGE.INTRODUCTION,
          source: OPPORTUNITY_SOURCE.INTERNET_ELIGIBILITY,
          applicationStage: APPLICATION_STAGE.INTRO_1,
        });

        return { opportunityId: created, wasCreated: true };
      },
      { ttlMs: 10_000 }
    );
  }

  /**
   * Resolve (find or create) an Opportunity for order placement.
   *
   * - If an OpportunityId is already provided, use it as-is.
   * - Otherwise, match only Introduction/Ready to avoid corrupting lifecycle tracking.
   * - If none found, create a new Opportunity in Post Processing stage.
   */
  async resolveForOrderPlacement(params: {
    accountId: string | null;
    orderType: OrderTypeValue;
    existingOpportunityId?: string | undefined;
  }): Promise<{ opportunityId: string | null; wasCreated: boolean }> {
    if (!params.accountId) return { opportunityId: null, wasCreated: false };

    const safeAccountId = assertSalesforceId(params.accountId, "accountId");

    if (params.existingOpportunityId) {
      const validated = assertSalesforceId(params.existingOpportunityId, "existingOpportunityId");
      return { opportunityId: validated, wasCreated: false };
    }

    const productType = this.mapOrderTypeToProductType(params.orderType);
    const lockKey = `opportunity:order:${safeAccountId}:${productType}`;

    return this.lockService.withLock(
      lockKey,
      async () => {
        const existing = await this.opportunities.findOpenOpportunityForAccount(
          safeAccountId,
          productType,
          { stages: OPPORTUNITY_MATCH_STAGES_ORDER_PLACEMENT }
        );

        if (existing) {
          return { opportunityId: existing, wasCreated: false };
        }

        const created = await this.opportunities.createOpportunity({
          accountId: safeAccountId,
          productType,
          stage: OPPORTUNITY_STAGE.POST_PROCESSING,
          source: OPPORTUNITY_SOURCE.ORDER_PLACEMENT,
          applicationStage: APPLICATION_STAGE.INTRO_1,
        });

        this.logger.log("Created new Opportunity for order placement", {
          accountIdTail: safeAccountId.slice(-4),
          opportunityIdTail: created.slice(-4),
          productType,
          orderType: params.orderType,
        });

        return { opportunityId: created, wasCreated: true };
      },
      { ttlMs: 10_000 }
    );
  }

  private mapOrderTypeToProductType(orderType: OrderTypeValue): OpportunityProductTypeValue {
    switch (orderType) {
      case ORDER_TYPE.INTERNET:
        return OPPORTUNITY_PRODUCT_TYPE.INTERNET;
      case ORDER_TYPE.SIM:
        return OPPORTUNITY_PRODUCT_TYPE.SIM;
      case ORDER_TYPE.VPN:
        return OPPORTUNITY_PRODUCT_TYPE.VPN;
      default:
        throw new Error(`Unsupported order type: ${orderType}`);
    }
  }
}
