const { buildRRRepairOrderPayload, buildMinimalRolaborFromJob } = require("./rr-job-helpers");
const { buildClientAndOpts } = require("./rr-lookup");
const CreateRRLogEvent = require("./rr-logger-event");
const { withRRRequestXml } = require("./rr-log-xml");
const { buildRRPreviewMetadata } = require("./rr-preview-metadata");
const { extractRrResponsibilityCenters } = require("./rr-responsibility-centers");
const CdkCalculateAllocations = require("./rr-calculate-allocations").default;
const { isEnhancedEarlyROEnabled, resolveRROpCodeFromBodyshop } = require("./rr-utils");

/**
 * Derive RR status information from response object.
 * @param rrRes
 * @returns {{status: *, statusCode: *|undefined, message}|null}
 */
const deriveRRStatus = (rrRes = {}) => {
  const data = rrRes.data || {};
  const tx = rrRes.statusBlocks && rrRes.statusBlocks.transaction;

  const pick = (obj, ...keys) => {
    if (!obj) return undefined;
    for (const k of keys) {
      if (obj[k] != null) return obj[k];
    }
    return undefined;
  };

  let status =
    pick(data.roStatus, "status", "Status") || pick(data, "status", "Status") || pick(tx, "status", "Status");

  let statusCode =
    pick(data.roStatus, "statusCode", "StatusCode") ||
    pick(data, "statusCode", "StatusCode") ||
    pick(tx, "statusCode", "StatusCode");

  let message = pick(data.roStatus, "message", "Message") || data.errorMessage || pick(tx, "message", "Message");

  // Last resort: parse from XML if present
  if ((!status || !message) && typeof rrRes.xml === "string") {
    const m = rrRes.xml.match(/<(?:GenTransStatus|TransStatus)\b([^>]*)>([^<]*)<\/(?:GenTransStatus|TransStatus)>/i);
    if (m) {
      const attrs = m[1] || "";
      const body = (m[2] || "").trim();
      const statusMatch = attrs.match(/\bStatus="([^"]*)"/i);
      const codeMatch = attrs.match(/\bStatusCode="([^"]*)"/i);
      if (!status && statusMatch) status = statusMatch[1];
      if (!statusCode && codeMatch) statusCode = codeMatch[1];
      if (!message && body) message = body;
    }
  }

  if (!status && !statusCode && !message) return null;

  return {
    status,
    statusCode: statusCode != null && statusCode !== "" ? statusCode : undefined,
    message: message || undefined
  };
};

const resolveRROpCode = (bodyshop, txEnvelope = {}) => {
  const resolvedBaseOpCode = resolveRROpCodeFromBodyshop(bodyshop);
  let opCodeOverride = txEnvelope?.opCode || txEnvelope?.opcode || txEnvelope?.op_code || null;

  if (!opCodeOverride) {
    const opPrefix = txEnvelope?.opPrefix ?? txEnvelope?.op_prefix ?? null;
    const opBase = txEnvelope?.opBase ?? txEnvelope?.op_base ?? null;
    const opSuffix = txEnvelope?.opSuffix ?? txEnvelope?.op_suffix ?? null;

    if (opPrefix || opBase || opSuffix) {
      const combined = `${opPrefix || ""}${opBase || ""}${opSuffix || ""}`.trim();
      if (combined) {
        opCodeOverride = combined;
      }
    }
  }

  if (!opCodeOverride && !resolvedBaseOpCode) return null;
  return String(opCodeOverride || resolvedBaseOpCode).trim() || null;
};

/**
 * Early RO Creation: Create a minimal RR Repair Order with basic info (customer, advisor, mileage, story).
 * Used when creating RO from convert button or admin page before full job export.
 * @param args
 * @returns {Promise<{success: boolean, data: *, roStatus: {status: *, statusCode: *|undefined, message}, statusBlocks: *|{}, customerNo: string, svId: *, roNo: *, xml: *}>}
 */
const createMinimalRRRepairOrder = async (args) => {
  const { bodyshop, job, advisorNo, selectedCustomer, txEnvelope, socket, svId } = args || {};

  if (!bodyshop) throw new Error("createMinimalRRRepairOrder: bodyshop is required");
  if (!job) throw new Error("createMinimalRRRepairOrder: job is required");
  if (advisorNo == null || String(advisorNo).trim() === "") {
    throw new Error("createMinimalRRRepairOrder: advisorNo is required for RR");
  }

  // Resolve customer number (accept multiple shapes)
  const selected = selectedCustomer?.customerNo || selectedCustomer?.custNo;
  if (!selected) throw new Error("createMinimalRRRepairOrder: selectedCustomer.custNo/customerNo is required");

  const { client, opts } = buildClientAndOpts(bodyshop);

  // For early RO creation we always "Insert" (create minimal RO)
  const finalOpts = {
    ...opts,
    envelope: {
      ...(opts?.envelope || {}),
      sender: {
        ...(opts?.envelope?.sender || {}),
        task: "BSMRO",
        referenceId: "Insert"
      }
    }
  };

  const story = txEnvelope?.story ? String(txEnvelope.story).trim() : null;
  const makeOverride = txEnvelope?.makeOverride ? String(txEnvelope.makeOverride).trim() : null;

  // Build minimal RO payload for early review mode.
  // We keep it lightweight, but include a single labor row when we can so Ignite
  // exposes the labor subsection for editing.
  const cleanVin =
    (job?.v_vin || "")
      .toString()
      .replace(/[^A-Za-z0-9]/g, "")
      .toUpperCase()
      .slice(0, 17) || undefined;

  // Resolve mileage - must be a positive number
  let mileageIn = txEnvelope?.kmin ?? job?.kmin ?? null;
  if (mileageIn != null) {
    mileageIn = parseInt(mileageIn, 10);
    if (isNaN(mileageIn) || mileageIn < 0) {
      mileageIn = null;
    }
  }

  CreateRRLogEvent(socket, "DEBUG", "Resolved mileage for early RO", {
    txEnvelopeKmin: txEnvelope?.kmin,
    jobKmin: job?.kmin,
    resolvedMileageIn: mileageIn
  });

  const enhancedEarlyROEnabled = isEnhancedEarlyROEnabled(bodyshop);
  const earlyRoOpCode = enhancedEarlyROEnabled ? resolveRROpCode(bodyshop, txEnvelope) : null;
  const earlyRoLabor = enhancedEarlyROEnabled
    ? buildMinimalRolaborFromJob(job, {
        opCode: earlyRoOpCode,
        payType: "Cust"
      })
    : null;

  const payload = {
    customerNo: String(selected),
    advisorNo: String(advisorNo),
    vin: cleanVin,
    departmentType: "B",
    outsdRoNo: job?.ro_number || job?.id || undefined,
    estimate: {
      parts: "0",
      labor: "0",
      total: "0.00"
    }
  };

  // Only add mileageIn if we have a valid value
  if (mileageIn != null && mileageIn >= 0) {
    payload.mileageIn = mileageIn;
  }

  // Add optional fields if present
  if (story) {
    payload.roComment = story;
  }
  if (makeOverride) {
    payload.makeOverride = makeOverride;
  }
  if (earlyRoLabor) {
    payload.rolabor = earlyRoLabor;
  }

  CreateRRLogEvent(socket, "INFO", "Creating minimal RR Repair Order (early creation)", {
    payload,
    enhancedEarlyROEnabled,
    earlyRoOpCode,
    hasRolabor: !!earlyRoLabor
  });

  const response = await client.createRepairOrder(payload, finalOpts);

  CreateRRLogEvent(
    socket,
    "INFO",
    "RR minimal Repair Order created",
    withRRRequestXml(response, { payload, response })
  );

  const data = response?.data || null;
  const statusBlocks = response?.statusBlocks || {};
  const roStatus = deriveRRStatus(response);

  const statusUpper = roStatus?.status ? String(roStatus.status).toUpperCase() : null;

  let success = false;

  if (statusUpper) {
    // Treat explicit FAILURE / ERROR as hard failures
    success = !["FAILURE", "ERROR"].includes(statusUpper);
  } else if (typeof response?.success === "boolean") {
    // Fallback to library boolean if no explicit status
    success = response.success;
  } else if (roStatus?.status) {
    success = String(roStatus.status).toUpperCase() === "SUCCESS";
  }

  // Extract canonical roNo for later updates
  const roNo = data?.dmsRoNo ?? data?.outsdRoNo ?? roStatus?.dmsRoNo ?? null;

  return {
    success,
    data,
    roStatus,
    statusBlocks,
    customerNo: String(selected),
    svId,
    roNo,
    xml: response?.xml // expose XML for logging/diagnostics
  };
};

/**
 * Full Data Update: Update an existing RR Repair Order with complete job data (allocations, parts, labor).
 * Used during DMS post form when an early RO was already created.
 * @param args
 * @returns {Promise<{success: boolean, data: *, roStatus: {status: *, statusCode: *|undefined, message}, statusBlocks: *|{}, customerNo: string, svId: *, roNo: *, xml: *}>}
 */
const updateRRRepairOrderWithFullData = async (args) => {
  const { bodyshop, job, advisorNo, selectedCustomer, txEnvelope, socket, svId, roNo } = args || {};

  if (!bodyshop) throw new Error("updateRRRepairOrderWithFullData: bodyshop is required");
  if (!job) throw new Error("updateRRRepairOrderWithFullData: job is required");
  if (advisorNo == null || String(advisorNo).trim() === "") {
    throw new Error("updateRRRepairOrderWithFullData: advisorNo is required for RR");
  }
  if (!roNo) throw new Error("updateRRRepairOrderWithFullData: roNo is required for update");

  // Resolve customer number (accept multiple shapes)
  const selected = selectedCustomer?.customerNo || selectedCustomer?.custNo;
  if (!selected) throw new Error("updateRRRepairOrderWithFullData: selectedCustomer.custNo/customerNo is required");

  const { client, opts } = buildClientAndOpts(bodyshop);

  // For full data update after early RO, use the RR update route.
  const finalOpts = {
    ...opts,
    envelope: {
      ...(opts?.envelope || {}),
      sender: {
        ...(opts?.envelope?.sender || {}),
        task: "BSMRO",
        referenceId: "Update"
      }
    }
  };

  const story = txEnvelope?.story ? String(txEnvelope.story).trim() : null;
  const makeOverride = txEnvelope?.makeOverride ? String(txEnvelope.makeOverride).trim() : null;

  // RR-only extras
  let rrCentersConfig = null;
  let allocations = null;
  const opCode = resolveRROpCode(bodyshop, txEnvelope);

  // 1) Responsibility center config (for visibility / debugging)
  try {
    rrCentersConfig = extractRrResponsibilityCenters(bodyshop);

    CreateRRLogEvent(socket, "SILLY", "RR responsibility centers resolved", {
      hasCenters: !!bodyshop.md_responsibility_centers,
      profitCenters: Object.keys(rrCentersConfig?.profitsByName || {}),
      costCenters: Object.keys(rrCentersConfig?.costsByName || {}),
      dmsCostDefaults: rrCentersConfig?.dmsCostDefaults || {},
      dmsProfitDefaults: rrCentersConfig?.dmsProfitDefaults || {}
    });
  } catch (e) {
    CreateRRLogEvent(socket, "ERROR", "Failed to resolve RR responsibility centers", {
      message: e?.message,
      stack: e?.stack
    });
  }

  // 2) Allocations (sales + cost by center, with rr_* metadata already attached)
  try {
    const allocResult = await CdkCalculateAllocations(socket, job.id);

    // We only need the per-center job allocations for RO.GOG / ROLABOR.
    allocations = Array.isArray(allocResult?.jobAllocations) ? allocResult.jobAllocations : [];

    CreateRRLogEvent(socket, "INFO", "RR allocations resolved for update", {
      hasAllocations: allocations.length > 0,
      count: allocations.length,
      allocationsPreview: allocations.slice(0, 2).map((a) => ({
        type: a?.type,
        code: a?.code,
        laborSale: a?.laborSale,
        laborCost: a?.laborCost,
        partsSale: a?.partsSale,
        partsCost: a?.partsCost
      })),
      taxAllocCount: Array.isArray(allocResult?.taxAllocArray) ? allocResult.taxAllocArray.length : 0,
      ttlAdjCount: Array.isArray(allocResult?.ttlAdjArray) ? allocResult.ttlAdjArray.length : 0,
      ttlTaxAdjCount: Array.isArray(allocResult?.ttlTaxAdjArray) ? allocResult.ttlTaxAdjArray.length : 0
    });
  } catch (e) {
    CreateRRLogEvent(socket, "ERROR", "Failed to calculate RR allocations", {
      message: e?.message,
      stack: e?.stack
    });
    // Proceed with a header-only update if allocations fail.
    allocations = [];
  }

  CreateRRLogEvent(socket, "SILLY", "RR OP config resolved", {
    opCode,
    baseFromConfig: resolveRROpCodeFromBodyshop(bodyshop)
  });

  // Build full RO payload for update with allocations
  const payload = buildRRRepairOrderPayload({
    bodyshop,
    job,
    selectedCustomer: { customerNo: String(selected), custNo: String(selected) },
    advisorNo: String(advisorNo),
    story,
    makeOverride,
    allocations,
    opCode
  });

  // Update the existing RO created during the early RO step.
  payload.finalUpdate = "N";
  payload.roNo = String(roNo);
  payload.outsdRoNo = job?.ro_number || job?.id || undefined;

  // RR update needs a ROLABOR row for every ROGOG JobNo, but rejects zero-price placeholders.
  // buildRolaborFromRogog mirrors the GOG price into each row, so keep the full 1:1 set.
  if (payload.rolabor?.ops?.length && payload.rogg?.ops?.length) {
    const roggJobNos = new Set(payload.rogg.ops.map((op) => String(op.jobNo)));

    payload.rolabor.ops = payload.rolabor.ops.filter((op) => roggJobNos.has(String(op?.jobNo)));

    if (!payload.rolabor.ops.length) {
      delete payload.rolabor;
    }
  }

  CreateRRLogEvent(socket, "INFO", "Preparing full data update for existing RR RO", {
    roNo: String(roNo),
    hasRolabor: !!payload.rolabor,
    rolaborCount: payload.rolabor?.ops?.length || 0,
    hasRogg: !!payload.rogg,
    payload
  });

  const response = await client.updateRepairOrder(payload, finalOpts);

  CreateRRLogEvent(
    socket,
    "INFO",
    "RR full data update sent for existing RO",
    withRRRequestXml(response, {
      roNo: String(roNo),
      hasRolabor: !!payload.rolabor,
      rolaborCount: payload.rolabor?.ops?.length || 0,
      hasRogg: !!payload.rogg,
      payload,
      response
    })
  );

  const data = response?.data || null;
  const statusBlocks = response?.statusBlocks || {};
  const roStatus = deriveRRStatus(response);

  const statusUpper = roStatus?.status ? String(roStatus.status).toUpperCase() : null;

  let success = false;

  if (statusUpper) {
    success = !["FAILURE", "ERROR"].includes(statusUpper);
  } else if (typeof response?.success === "boolean") {
    success = response.success;
  } else if (roStatus?.status) {
    success = String(roStatus.status).toUpperCase() === "SUCCESS";
  }

  const exportResult = {
    success,
    data,
    roStatus,
    statusBlocks,
    customerNo: String(selected),
    svId,
    roNo: String(roNo),
    xml: response?.xml
  };
  exportResult.rrPreview = buildRRPreviewMetadata({ payload, result: exportResult });
  return exportResult;
};

/**
 * LEGACY: Step 1: Export a job to RR as a new Repair Order with full data.
 * This is the original function - kept for backward compatibility if shops don't use early RO creation.
 * @param args
 * @returns {Promise<{success: boolean, data: *, roStatus: {status: *, statusCode: *|undefined, message}, statusBlocks: *|{}, customerNo: string, svId: *, roNo: *, xml: *}>}
 */
const exportJobToRR = async (args) => {
  const { bodyshop, job, advisorNo, selectedCustomer, txEnvelope, socket, svId } = args || {};

  if (!bodyshop) throw new Error("exportJobToRR: bodyshop is required");
  if (!job) throw new Error("exportJobToRR: job is required");
  if (advisorNo == null || String(advisorNo).trim() === "") {
    throw new Error("exportJobToRR: advisorNo is required for RR");
  }

  // Resolve customer number (accept multiple shapes)
  const selected = selectedCustomer?.customerNo || selectedCustomer?.custNo;
  if (!selected) throw new Error("exportJobToRR: selectedCustomer.custNo/customerNo is required");

  const { client, opts } = buildClientAndOpts(bodyshop);

  // For step 1 we always "Insert" (create). Finalize handles the update.
  const finalOpts = {
    ...opts,
    envelope: {
      ...(opts?.envelope || {}),
      sender: {
        ...(opts?.envelope?.sender || {}),
        task: "BSMRO",
        referenceId: "Insert"
      }
    }
  };

  const story = txEnvelope?.story ? String(txEnvelope.story).trim() : null;
  const makeOverride = txEnvelope?.makeOverride ? String(txEnvelope.makeOverride).trim() : null;

  // RR-only extras
  let rrCentersConfig = null;
  let allocations = null;
  const opCode = resolveRROpCode(bodyshop, txEnvelope);

  // 1) Responsibility center config (for visibility / debugging)
  try {
    rrCentersConfig = extractRrResponsibilityCenters(bodyshop);

    CreateRRLogEvent(socket, "SILLY", "RR responsibility centers resolved", {
      hasCenters: !!bodyshop.md_responsibility_centers,
      profitCenters: Object.keys(rrCentersConfig?.profitsByName || {}),
      costCenters: Object.keys(rrCentersConfig?.costsByName || {}),
      dmsCostDefaults: rrCentersConfig?.dmsCostDefaults || {},
      dmsProfitDefaults: rrCentersConfig?.dmsProfitDefaults || {}
    });
  } catch (e) {
    CreateRRLogEvent(socket, "ERROR", "Failed to resolve RR responsibility centers", {
      message: e?.message,
      stack: e?.stack
    });
  }

  // 2) Allocations (sales + cost by center, with rr_* metadata already attached)
  try {
    const allocResult = await CdkCalculateAllocations(socket, job.id);

    // We only need the per-center job allocations for RO.GOG / ROLABOR.
    allocations = Array.isArray(allocResult?.jobAllocations) ? allocResult.jobAllocations : [];

    CreateRRLogEvent(socket, "SILLY", "RR allocations resolved", {
      hasAllocations: allocations.length > 0,
      count: allocations.length,
      taxAllocCount: Array.isArray(allocResult?.taxAllocArray) ? allocResult.taxAllocArray.length : 0,
      ttlAdjCount: Array.isArray(allocResult?.ttlAdjArray) ? allocResult.ttlAdjArray.length : 0,
      ttlTaxAdjCount: Array.isArray(allocResult?.ttlTaxAdjArray) ? allocResult.ttlTaxAdjArray.length : 0
    });
  } catch (e) {
    CreateRRLogEvent(socket, "ERROR", "Failed to calculate RR allocations", {
      message: e?.message,
      stack: e?.stack
    });
    // Proceed with a header-only RO if allocations fail.
    allocations = [];
  }

  CreateRRLogEvent(socket, "SILLY", "RR OP config resolved", {
    opCode,
    baseFromConfig: resolveRROpCodeFromBodyshop(bodyshop)
  });

  // Build RO payload for create.
  //
  // NOTE:
  //   - bodyshop + allocations + opCode + taxCode are used only to build the
  //     value object expected by reynolds-rome-client (header + rogg + rolabor + tax).
  const payload = buildRRRepairOrderPayload({
    bodyshop,
    job,
    selectedCustomer: { customerNo: String(selected), custNo: String(selected) },
    advisorNo: String(advisorNo),
    story,
    makeOverride,
    allocations,
    opCode
  });

  const response = await client.createRepairOrder(payload, finalOpts);

  CreateRRLogEvent(socket, "INFO", "RR raw Repair Order created", withRRRequestXml(response, { payload, response }));

  const data = response?.data || null;
  const statusBlocks = response?.statusBlocks || {};
  const roStatus = deriveRRStatus(response);

  const statusUpper = roStatus?.status ? String(roStatus.status).toUpperCase() : null;

  let success = false;

  if (statusUpper) {
    // Treat explicit FAILURE / ERROR as hard failures
    success = !["FAILURE", "ERROR"].includes(statusUpper);
  } else if (typeof response?.success === "boolean") {
    // Fallback to library boolean if no explicit status
    success = response.success;
  } else if (roStatus?.status) {
    success = String(roStatus.status).toUpperCase() === "SUCCESS";
  }

  // Extract canonical roNo you'll need for finalize step
  const roNo = data?.dmsRoNo ?? data?.outsdRoNo ?? roStatus?.dmsRoNo ?? null;

  const exportResult = {
    success,
    data,
    roStatus,
    statusBlocks,
    customerNo: String(selected),
    svId,
    roNo,
    xml: response?.xml // expose XML for logging/diagnostics
  };
  exportResult.rrPreview = buildRRPreviewMetadata({ payload, result: exportResult });
  return exportResult;
};

/**
 * Step 2: Finalize an existing RR Repair Order (previously created).
 * @param args
 * @returns {Promise<{success: boolean, data: *, roStatus: {status: *, statusCode: *|undefined, message}, statusBlocks: *|{}, xml: *}>}
 */
const finalizeRRRepairOrder = async (args) => {
  const { bodyshop, job, advisorNo, customerNo, roNo, vin, socket } = args || {};

  if (!bodyshop) throw new Error("finalizeRRRepairOrder: bodyshop is required");
  if (!job) throw new Error("finalizeRRRepairOrder: job is required");
  if (!advisorNo) throw new Error("finalizeRRRepairOrder: advisorNo is required");
  if (!customerNo) throw new Error("finalizeRRRepairOrder: customerNo is required");

  // The external (Outsd) RO is our deterministic fallback and correlation id.
  const externalRo = job?.ro_number ?? job?.id;
  if (externalRo == null) {
    throw new Error("finalizeRRRepairOrder: outsdRoNo (job.ro_number/id) is required");
  }

  // Prefer DMS RO for update; fall back to external when DMS RO isn't known
  const roNoToSend = roNo ? String(roNo) : String(externalRo);

  const { client, opts } = buildClientAndOpts(bodyshop);
  const finalOpts = {
    ...opts,
    envelope: {
      ...(opts?.envelope || {}),
      sender: {
        ...(opts?.envelope?.sender || {}),
        task: "BSMRO",
        referenceId: "Update"
      }
    }
  };

  const cleanVin =
    (job?.v_vin || vin || "")
      .toString()
      .replace(/[^A-Za-z0-9]/g, "")
      .toUpperCase()
      .slice(0, 17) || undefined;

  // IMPORTANT: include "roNo" on updates (RR requires it). Also send outsdRoNo for correlation.
  const payload = {
    roNo: roNoToSend, // ✅ REQUIRED BY RR on update
    outsdRoNo: String(externalRo),
    finalUpdate: "Y",
    departmentType: "B",
    customerNo: String(customerNo),
    advisorNo: String(advisorNo),
    vin: cleanVin,
    mileageOut: job?.kmout,
    estimate: { estimateType: "Final" }
  };

  CreateRRLogEvent(socket, "INFO", "Finalizing RR Repair Order", {
    roNo: roNoToSend,
    outsdRoNo: String(externalRo),
    customerNo: String(customerNo),
    advisorNo: String(advisorNo)
  });

  const rrRes = await client.updateRepairOrder(payload, finalOpts);

  CreateRRLogEvent(
    socket,
    "SILLY",
    "RR Repair Order finalized",
    withRRRequestXml(rrRes, {
      payload,
      response: rrRes
    })
  );

  const data = rrRes?.data || null;
  const statusBlocks = rrRes?.statusBlocks || {};
  const roStatus = deriveRRStatus(rrRes);

  const statusUpper = roStatus?.status ? String(roStatus.status).toUpperCase() : null;

  let success = false;
  if (statusUpper) {
    success = !["FAILURE", "ERROR"].includes(statusUpper);
  } else if (typeof rrRes?.success === "boolean") {
    success = rrRes.success;
  } else if (roStatus?.status) {
    success = String(roStatus.status).toUpperCase() === "SUCCESS";
  }

  return {
    success,
    data,
    roStatus,
    statusBlocks,
    xml: rrRes?.xml
  };
};

module.exports = {
  exportJobToRR,
  createMinimalRRRepairOrder,
  updateRRRepairOrderWithFullData,
  finalizeRRRepairOrder,
  deriveRRStatus
};
