import { CWForecastItem } from "../modules/cw-utils/opportunities/opportunity.types";

/**
 * Forecast Product Controller
 *
 * Domain model class that encapsulates a ConnectWise Forecast Item (product/
 * revenue line item on an opportunity). Forecast products are not persisted
 * locally — all data is sourced directly from the ConnectWise API.
 */
export class ForecastProductController {
  private static readonly PROCUREMENT_NOTES_FIELD_ID = 29;
  private static readonly PRODUCT_NARRATIVE_FIELD_ID = 46;

  public readonly cwForecastId: number;
  public forecastDescription: string;

  public opportunityCwId: number | null;
  public opportunityName: string | null;

  public quantity: number;

  public statusCwId: number | null;
  public statusName: string | null;

  public catalogItemCwId: number | null;
  public catalogItemIdentifier: string | null;

  public productDescription: string;
  public customerDescription: string | null;
  public description: string | null;
  public procurementNotes: string | null;
  public productNarrative: string | null;
  public productClass: string;
  public forecastType: string;

  public revenue: number;
  public cost: number;
  public margin: number;
  public percentage: number;

  public includeFlag: boolean;
  public linkFlag: boolean;
  public recurringFlag: boolean;
  public taxableFlag: boolean;

  public recurringRevenue: number;
  public recurringCost: number;
  public cycles: number;

  public sequenceNumber: number;
  public subNumber: number;
  public quoteWerksQuantity: number;

  public cwLastUpdated: Date | null;
  public cwUpdatedBy: string | null;

  // Pricing data (from local ProductData)
  public unitPrice: number;
  public listPrice: number;
  public discount: number;

  // Cancellation data (from procurement products endpoint)
  public cancelledFlag: boolean;
  public quantityCancelled: number;
  public cancelledReason: string | null;
  public cancelledBy: number | null;
  public cancelledDate: Date | null;

  // Internal inventory data (from local CatalogItem database)
  public onHand: number | null;
  public inStock: boolean | null;

  constructor(data: CWForecastItem) {
    this.cwForecastId = data.id;
    this.forecastDescription = data.forecastDescription;

    this.opportunityCwId = data.opportunity?.id ?? null;
    this.opportunityName = data.opportunity?.name ?? null;

    this.quantity = data.quantity;

    this.statusCwId = data.status?.id ?? null;
    this.statusName = data.status?.name ?? null;

    this.catalogItemCwId = data.catalogItem?.id ?? null;
    this.catalogItemIdentifier = data.catalogItem?.identifier ?? null;

    this.productDescription = data.productDescription;
    this.customerDescription = data.customerDescription ?? null;
    this.description = null;
    this.procurementNotes =
      data.procurementNotes ??
      data.customFields
        ?.find(
          (f) => f.id === ForecastProductController.PROCUREMENT_NOTES_FIELD_ID
        )
        ?.value?.toString() ??
      null;
    this.productNarrative =
      data.productNarrative ??
      data.customFields
        ?.find(
          (f) => f.id === ForecastProductController.PRODUCT_NARRATIVE_FIELD_ID
        )
        ?.value?.toString() ??
      null;
    this.productClass = data.productClass;
    this.forecastType = data.forecastType;

    this.revenue = data.revenue;
    this.cost = data.cost;
    this.margin = data.margin;
    this.percentage = data.percentage;

    this.includeFlag = data.includeFlag ?? false;
    this.linkFlag = data.linkFlag ?? false;
    this.recurringFlag = data.recurringFlag ?? false;
    this.taxableFlag = data.taxableFlag ?? false;

    this.recurringRevenue = data.recurringRevenue ?? 0;
    this.recurringCost = data.recurringCost ?? 0;
    this.cycles = data.cycles ?? 0;

    this.sequenceNumber = data.sequenceNumber ?? 0;
    this.subNumber = data.subNumber ?? 0;
    this.quoteWerksQuantity = data.quoteWerksQuantity ?? 0;

    this.cwLastUpdated = data._info?.lastUpdated
      ? new Date(data._info.lastUpdated)
      : null;
    this.cwUpdatedBy = data._info?.updatedBy ?? null;

    // Pricing defaults — enriched later via applyPricingData()
    this.unitPrice = 0;
    this.listPrice = 0;
    this.discount = 0;

    // Cancellation defaults — enriched later via applyCancellationData()
    this.cancelledFlag = false;
    this.quantityCancelled = 0;
    this.cancelledReason = null;
    this.cancelledBy = null;
    this.cancelledDate = null;

    // Inventory defaults — enriched later via applyInventoryData()
    this.onHand = null;
    this.inStock = null;
  }

  /**
   * Apply Cancellation Data
   *
   * Enriches this forecast product with cancellation data from the
   * procurement products endpoint.
   */
  /**
   * Apply Procurement Custom Fields
   *
   * Enriches this forecast product with custom field data from the
   * procurement products endpoint (the forecast endpoint does not
   * return customFields).
   */
  public applyProcurementCustomFields(data: {
    customFields?: Array<{ id: number; value?: unknown }>;
  }): void {
    const notes = data.customFields
      ?.find(
        (f) => f.id === ForecastProductController.PROCUREMENT_NOTES_FIELD_ID
      )
      ?.value?.toString();
    if (notes) {
      this.procurementNotes = notes;
    }

    const narrative = data.customFields
      ?.find(
        (f) => f.id === ForecastProductController.PRODUCT_NARRATIVE_FIELD_ID
      )
      ?.value?.toString();
    if (narrative) {
      this.productNarrative = narrative;
    }
  }

  public applyCancellationData(data: {
    cancelledFlag?: boolean;
    quantityCancelled?: number;
    cancelledReason?: string;
    cancelledBy?: number;
    cancelledDate?: string;
  }): void {
    this.cancelledFlag = data.cancelledFlag ?? false;
    this.quantityCancelled = data.quantityCancelled ?? 0;
    this.cancelledReason = data.cancelledReason ?? null;
    this.cancelledBy = data.cancelledBy ?? null;
    this.cancelledDate = data.cancelledDate
      ? new Date(data.cancelledDate)
      : null;
  }

  /**
   * Apply Inventory Data
   *
   * Enriches this forecast product with internal inventory data from
   * the local CatalogItem database.
   */
  public applyInventoryData(data: { onHand: number }): void {
    this.onHand = data.onHand;
    this.inStock = data.onHand > 0;
  }

  /**
   * Profit
   *
   * Returns the calculated profit (revenue - cost).
   */
  public get profit(): number {
    return this.revenue - this.cost;
  }

  /**
   * Effective Quantity
   *
   * Returns the quantity adjusted for cancellations (minimum 0).
   */
  public get effectiveQuantity(): number {
    if (this.cancellationType === "full") return 0;
    return Math.max(0, this.quantity - this.quantityCancelled);
  }

  /**
   * Effective Revenue
   *
   * Returns the revenue adjusted proportionally for cancelled units.
   */
  public get effectiveRevenue(): number {
    if (this.cancellationType === "full" || this.quantity <= 0) return 0;
    const unitPrice = this.revenue / this.quantity;
    return unitPrice * this.effectiveQuantity;
  }

  /**
   * Effective Cost
   *
   * Returns the cost adjusted proportionally for cancelled units.
   */
  public get effectiveCost(): number {
    if (this.cancellationType === "full" || this.quantity <= 0) return 0;
    const unitCost = this.cost / this.quantity;
    return unitCost * this.effectiveQuantity;
  }

  /**
   * Cancelled
   *
   * Returns true if the forecast item has been cancelled (fully or partially).
   */
  public get cancelled(): boolean {
    return this.cancelledFlag;
  }

  /**
   * Cancellation Type
   *
   * Returns the type of cancellation:
   * - `"full"` — all units have been cancelled (`quantityCancelled >= quantity`)
   * - `"partial"` — some units cancelled but not all
   * - `null` — not cancelled
   */
  public get cancellationType(): "full" | "partial" | null {
    if (!this.cancelledFlag || this.quantityCancelled <= 0) return null;
    return this.quantityCancelled >= this.quantity ? "full" : "partial";
  }

  /**
   * To JSON
   *
   * Serializes the forecast product into a safe, API-friendly object.
   */
  public toJson(): Record<string, any> {
    return {
      id: this.cwForecastId,
      forecastDescription: this.forecastDescription,
      opportunity: this.opportunityCwId
        ? { id: this.opportunityCwId, name: this.opportunityName }
        : null,
      quantity: this.quantity,
      status: this.statusCwId
        ? { id: this.statusCwId, name: this.statusName }
        : null,
      cancelled: this.cancelled,
      cancellationType: this.cancellationType,
      quantityCancelled: this.quantityCancelled,
      cancelledReason: this.cancelledReason,
      cancelledDate: this.cancelledDate,
      catalogItem: this.catalogItemCwId
        ? { id: this.catalogItemCwId, identifier: this.catalogItemIdentifier }
        : null,
      productDescription: this.productDescription,
      customerDescription: this.customerDescription,
      procurementNotes: this.procurementNotes,
      productNarrative: this.productNarrative,
      productClass: this.productClass,
      forecastType: this.forecastType,
      revenue: this.revenue,
      cost: this.cost,
      margin: this.margin,
      profit: this.profit,
      effectiveQuantity: this.effectiveQuantity,
      effectiveRevenue: this.effectiveRevenue,
      effectiveCost: this.effectiveCost,
      percentage: this.percentage,
      includeFlag: this.includeFlag,
      linkFlag: this.linkFlag,
      recurringFlag: this.recurringFlag,
      taxableFlag: this.taxableFlag,
      recurringRevenue: this.recurringRevenue,
      recurringCost: this.recurringCost,
      cycles: this.cycles,
      sequenceNumber: this.sequenceNumber,
      subNumber: this.subNumber,
      cwLastUpdated: this.cwLastUpdated,
      cwUpdatedBy: this.cwUpdatedBy,
      onHand: this.onHand,
      inStock: this.inStock,
      unitPrice: this.unitPrice,
      listPrice: this.listPrice,
      discount: this.discount,
    };
  }

  /**
   * Apply Pricing Data
   *
   * Enriches this forecast product with pricing data from the local
   * ProductData table.
   */
  public applyPricingData(data: {
    unitPrice?: number;
    listPrice?: number;
    discount?: number;
  }): void {
    this.unitPrice = data.unitPrice ?? 0;
    this.listPrice = data.listPrice ?? 0;
    this.discount = data.discount ?? 0;
  }
}
