/**
 * Opportunity Collector Translation
 *
 * Maps opportunities from the collector (dalpuri) fetchOpportunities schema
 * to the internal database/CW API schema for normalization and storage.
 */

/**
 * Raw collector opportunity shape from fetchOpportunities.
 * This matches the MSSQL source structure with snake_case fields.
 */
export interface CollectorOpportunity {
  // Core IDs and identifiers
  soRecId?: number | string;
  recId?: number | string;
  id?: number | string;
  opportunityRecId?: number | string;
  soNumber?: string;

  // Main fields
  soDescription?: string; // opportunity name
  notes?: string;
  customerPO?: string;
  source?: string | null;

  // Status and type
  soPipeline?: { recId?: number; description?: string } | null;
  soOppStatus?: { recId?: number; description?: string } | null;
  soType?: { recId?: number; description?: string } | null;
  soUrgency?: { recId?: number; description?: string } | null;
  soInterest?: { recId?: number; description?: string } | null;

  // Relationships
  company?: {
    recId?: number;
    companyIdentifier?: string;
    companyName?: string;
  } | null;
  contact?: { recId?: number; contactName?: string } | null;
  companyAddress?: { addressRecId?: number; addressName?: string } | null;

  // Sales refs
  ownerLevel?: {
    recId?: number;
    memberName?: string;
    memberIdentifier?: string;
  } | null;

  // Financial / admin
  billingTerms?: { recId?: number; description?: string } | null;
  taxCode?: { recId?: number; description?: string } | null;
  currency?: { recId?: number; currencyCode?: string } | null;

  // Dates
  expectedCloseDate?: string | null;
  dateBecameLead?: string | null;
  closedDate?: string | null;
  pipelineChangeDate?: string | null;

  // Probability / closed
  closedFlag?: boolean;
  approvedByMember?: { recId?: number; memberName?: string } | null;
  rejectedByMember?: { recId?: number; memberName?: string } | null;

  // Other includes (less commonly mapped to Opportunity table)
  billingUnit?: { recId?: number; description?: string } | null;
  contractType?: { recId?: number; description?: string } | null;
  pmProject?: { recId?: number; projectName?: string } | null;
  marketingCampaign?: { recId?: number; description?: string } | null;
  agrType?: { recId?: number; description?: string } | null;
  srService?: { recId?: number; description?: string } | null;
  shipToCompany?: unknown | null;
  shipToContact?: unknown | null;
  shipToCompanyAddress?: unknown | null;
  billToCompany?: unknown | null;
  billToContact?: unknown | null;
  billToCompanyAddress?: unknown | null;
  techContact?: unknown | null;
  activities?: unknown[];
  opportunityNotes?: unknown[];
  forecastItems?: unknown[];
  opportunityContacts?: unknown[];
  contacts?: unknown[];

  // Metadata
  _info?: { lastUpdated?: string; dateEntered?: string };
}

type NumberLike = number | string | null | undefined;

/**
 * Normalized opportunity shape that maps to the DB Opportunity table.
 * Output from normalizeCollectorOpportunity().
 */
export interface NormalizedCollectorOpportunity {
  // Core identifiers (required for upsert)
  cwOpportunityId: number;

  // Basic info
  name: string;
  notes: string | null;
  customerPO: string | null;
  source: string | null;

  // Status / type references
  typeName: string | null;
  typeCwId: number | null;
  stageName: string | null;
  stageCwId: number | null;
  statusName: string | null;
  statusCwId: number | null;
  priorityName: string | null;
  priorityCwId: number | null;
  ratingName: string | null;
  ratingCwId: number | null;

  // Sales rep references
  primarySalesRepName: string | null;
  primarySalesRepIdentifier: string | null;
  primarySalesRepCwId: number | null;
  secondarySalesRepName: string | null;
  secondarySalesRepIdentifier: string | null;
  secondarySalesRepCwId: number | null;

  // Company / contact / site references
  companyCwId: number | null;
  companyName: string | null;
  contactCwId: number | null;
  contactName: string | null;
  siteCwId: number | null;
  siteName: string | null;

  // Campaign / location references
  campaignName: string | null;
  campaignCwId: number | null;
  locationName: string | null;
  locationCwId: number | null;
  departmentName: string | null;
  departmentCwId: number | null;

  // Dates
  expectedCloseDate: Date | null;
  pipelineChangeDate: Date | null;
  dateBecameLead: Date | null;
  closedDate: Date | null;
  closedFlag: boolean;
  closedByName: string | null;
  closedByCwId: number | null;

  // Financial
  totalSalesTax: number;
  probability: number | null;

  // Metadata
  cwLastUpdated: Date | null;
  cwDateEntered: Date | null;
}

/**
 * Helper: parse a date string to Date or return null.
 */
const parseDate = (dateString: string | null | undefined): Date | null => {
  if (!dateString) return null;
  try {
    const d = new Date(dateString);
    return isNaN(d.getTime()) ? null : d;
  } catch {
    return null;
  }
};

const parseNumber = (value: NumberLike): number | null => {
  if (typeof value === "number" && Number.isFinite(value)) return value;
  if (typeof value === "string" && value.trim().length > 0) {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : null;
  }
  return null;
};

/**
 * Helper: extract ID from nested object.
 */
const getId = (obj: unknown): number | null => {
  if (!obj || typeof obj !== "object") return null;
  const id = (obj as Record<string, unknown>).recId;
  const parsed = parseNumber(id as NumberLike);
  if (!parsed) return null;
  return parsed > 0 ? parsed : null;
};

const getOpportunityId = (item: CollectorOpportunity): number => {
  const candidate =
    parseNumber(item.soRecId) ??
    parseNumber(item.recId) ??
    parseNumber(item.id) ??
    parseNumber(item.opportunityRecId) ??
    0;
  return candidate > 0 ? candidate : 0;
};

/**
 * Helper: extract description/name from nested object.
 */
const getName = (obj: unknown, fallback = ""): string => {
  if (!obj || typeof obj !== "object") return fallback;
  const desc = (obj as Record<string, unknown>).description;
  const name = (obj as Record<string, unknown>).memberName;
  const result =
    typeof desc === "string"
      ? desc
      : typeof name === "string"
        ? name
        : fallback;
  return result || fallback;
};

/**
 * Normalize a collector opportunity into the internal DB schema.
 *
 * Handles field mapping, type conversions, and null coercion.
 */
export const normalizeCollectorOpportunity = (
  item: CollectorOpportunity,
): NormalizedCollectorOpportunity => {
  const cwOpportunityId = getOpportunityId(item);
  if (cwOpportunityId <= 0) {
    throw new Error("Collector opportunity missing ID field (expected soRecId, recId, id, or opportunityRecId)");
  }

  // Build normalized object mapping collector fields to DB schema
  return {
    cwOpportunityId,

    // Basic info
    name: item.soDescription ?? "",
    notes: item.notes ?? null,
    customerPO: item.customerPO ?? null,
    source: item.source ?? null,

    // Status / type (collector uses soPipeline/soOppStatus as CW stage/status)
    typeName: getName(item.soType),
    typeCwId: getId(item.soType),
    // Map soPipeline to stageName/stageCwId
    stageName: getName(item.soPipeline),
    stageCwId: getId(item.soPipeline),
    // Map soOppStatus to statusName/statusCwId
    statusName: getName(item.soOppStatus),
    statusCwId: getId(item.soOppStatus),
    // Priority / rating: soUrgency and soInterest can map here if needed
    priorityName: getName(item.soUrgency),
    priorityCwId: getId(item.soUrgency),
    ratingName: getName(item.soInterest),
    ratingCwId: getId(item.soInterest),

    // Sales rep references (ownerLevel is primary in collector)
    primarySalesRepName: item.ownerLevel
      ? ((item.ownerLevel as any).memberName ?? null)
      : null,
    primarySalesRepIdentifier: item.ownerLevel
      ? ((item.ownerLevel as any).memberIdentifier ?? null)
      : null,
    primarySalesRepCwId: getId(item.ownerLevel),
    secondarySalesRepName: null,
    secondarySalesRepIdentifier: null,
    secondarySalesRepCwId: null,

    // Company / contact / site references
    companyCwId: getId(item.company),
    companyName: item.company
      ? ((item.company as any).companyName ?? null)
      : null,
    contactCwId: getId(item.contact),
    contactName: item.contact
      ? ((item.contact as any).contactName ?? null)
      : null,
    siteCwId: getId(item.companyAddress),
    siteName: item.companyAddress
      ? ((item.companyAddress as any).addressName ?? null)
      : null,

    // Campaign / location (from includeRelations)
    campaignName: getName(item.marketingCampaign),
    campaignCwId: getId(item.marketingCampaign),
    locationName: null,
    locationCwId: null,
    departmentName: null,
    departmentCwId: null,

    // Dates
    expectedCloseDate: parseDate(item.expectedCloseDate),
    pipelineChangeDate: parseDate(item.pipelineChangeDate),
    dateBecameLead: parseDate(item.dateBecameLead),
    closedDate: parseDate(item.closedDate),
    closedFlag: item.closedFlag ?? false,
    closedByName: item.approvedByMember
      ? ((item.approvedByMember as any).memberName ?? null)
      : item.rejectedByMember
        ? ((item.rejectedByMember as any).memberName ?? null)
        : null,
    closedByCwId: getId(item.approvedByMember) ?? getId(item.rejectedByMember),

    // Financial
    totalSalesTax: 0, // Not provided by collector; will be 0
    probability: null, // Not directly provided; would need to compute from status/stage

    // Metadata
    cwLastUpdated: parseDate(item._info?.lastUpdated),
    cwDateEntered: parseDate(item._info?.dateEntered),
  };
};

/**
 * Normalize a collection of opportunities from the collector.
 */
export const normalizeCollectorOpportunities = (
  items: CollectorOpportunity[],
): Map<number, NormalizedCollectorOpportunity> => {
  const normalized = new Map<number, NormalizedCollectorOpportunity>();

  for (const item of items) {
    try {
      const norm = normalizeCollectorOpportunity(item);
      normalized.set(norm.cwOpportunityId, norm);
    } catch (err) {
      console.warn(
        `[opportunityCollectorTranslation] Failed to normalize item: ${err instanceof Error ? err.message : String(err)}`,
      );
    }
  }

  return normalized;
};
