/**
 * Get last 8 chars of a string, uppercased
 * @param v
 * @returns {string}
 */
const last8 = (v) => {
  return (String(v || "") || "").slice(-8).toUpperCase();
};

/**
 * Extract owner customer numbers from VIN-based blocks
 * @param blocks
 * @param jobVin
 * @returns {Set<any>}
 */
const ownersFromVinBlocks = (blocks = [], jobVin = null) => {
  const out = new Set();
  const want8 = jobVin ? last8(jobVin) : null;

  for (const blk of Array.isArray(blocks) ? blocks : []) {
    const serv = Array.isArray(blk?.ServVehicle) ? blk.ServVehicle : [];
    for (const sv of serv) {
      const svVin = String(sv?.Vehicle?.Vin || "");
      if (want8 && last8(svVin) !== want8) continue;
      const custNo = sv?.VehicleServInfo?.CustomerNo;
      if (custNo != null && String(custNo).trim() !== "") {
        out.add(String(custNo).trim());
      }
    }
  }
  return out;
};

/**
 * Make vehicle search payload from job data
 * @param job
 * @returns {null|{kind: string, license: string, maxResults: number}|{kind: string, vin: string, maxResults: number}}
 */
const makeVehicleSearchPayloadFromJob = (job) => {
  const vin = job?.v_vin;
  if (vin) return { kind: "vin", vin: String(vin).trim(), maxResults: 50 };
  const plate = job?.plate_no;
  if (plate) return { kind: "license", license: String(plate).trim(), maxResults: 50 };
  return null;
};

/**
 * Normalize customer candidates from VIN/name blocks, including address + owner flag
 * IMPORTANT: If no ServVehicle/CustomerNo exists (e.g. name-only hits), fall back to NameRecId.
 * @param res
 * @param ownersSet
 * @returns {any[]}
 */
const normalizeCustomerCandidates = (res, { ownersSet = null } = {}) => {
  const blocks = Array.isArray(res?.data) ? res.data : [];
  const out = [];

  const pickAddr = (addrArr) => {
    const arr = Array.isArray(addrArr) ? addrArr : addrArr ? [addrArr] : [];
    if (!arr.length) return null;

    const chosen = arr.find((a) => (a?.Type || a?.type || "").toString().toUpperCase() === "P") || arr[0];

    const line1 = chosen?.Addr1 ?? chosen?.AddressLine1 ?? chosen?.Line1 ?? chosen?.Street1 ?? undefined;
    const line2 = chosen?.Addr2 ?? chosen?.AddressLine2 ?? chosen?.Line2 ?? chosen?.Street2 ?? undefined;
    const city = chosen?.City ?? chosen?.city ?? undefined;
    const state = chosen?.State ?? chosen?.StateOrProvince ?? chosen?.state ?? undefined;
    const postalCode = chosen?.Zip ?? chosen?.PostalCode ?? chosen?.zip ?? undefined;
    const country = chosen?.Country ?? chosen?.CountryCode ?? chosen?.country ?? undefined;
    const county = chosen?.County ?? chosen?.county ?? undefined; // << added

    if (!line1 && !city && !state && !postalCode && !country && !county) return null;
    return { line1, line2, city, state, postalCode, country, county };
  };

  for (const blk of blocks) {
    const serv = Array.isArray(blk?.ServVehicle) ? blk.ServVehicle : [];
    const custNos = serv.map((sv) => sv?.VehicleServInfo?.CustomerNo).filter(Boolean);

    const nci = blk?.NameContactId;
    const ind = nci?.NameId?.IndName;
    const bus = nci?.NameId?.BusName;

    const personal = [ind?.FirstName ?? ind?.FName, ind?.LastName ?? ind?.LName].filter(Boolean).join(" ").trim();
    const company = bus?.CompanyName ?? bus?.BName;
    const name = (personal || company || "").trim();

    const address = pickAddr(nci?.Address);

    // fallback to NameRecId when no ServVehicle/CustomerNo exists (e.g., pure name search)
    const nameRecIdRaw = nci?.NameId?.NameRecId;
    const nameRecId = nameRecIdRaw != null ? String(nameRecIdRaw).trim() : "";

    if (Array.isArray(custNos) && custNos.length > 0) {
      for (const custNo of custNos) {
        const cno = String(custNo).trim();
        if (!cno) continue;

        const item = {
          custNo: cno,
          name: name || `Customer ${cno}`,
          address: address || undefined
        };

        if (ownersSet && ownersSet.has(cno)) {
          item.isVehicleOwner = true;
          item.vinOwner = true;
        }

        out.push(item);
      }
    } else if (nameRecId) {
      // Use NameRecId as the identifier
      const cno = nameRecId;
      const item = {
        custNo: cno,
        name: name || `Customer ${cno}`,
        address: address || undefined
      };
      out.push(item);
    }
  }

  // Deduplicate by custNo, merge owner/address flags
  const byId = new Map();
  for (const c of out) {
    const key = (c.custNo || "").trim();
    if (!key) continue;
    const prev = byId.get(key);
    if (!prev) byId.set(key, c);
    else {
      byId.set(key, {
        ...prev,
        isVehicleOwner: prev.isVehicleOwner || c.isVehicleOwner,
        vinOwner: prev.vinOwner || c.vinOwner,
        address: prev.address || c.address
      });
    }
  }

  return Array.from(byId.values());
};

/**
 * Read advisor number from payload or cached value
 * @param payload
 * @param cached
 * @returns {string|null}
 */
const readAdvisorNo = (payload, cached) => {
  const tx = payload?.txEnvelope || payload?.envelope || {};

  const get = (v) => (v != null && String(v).trim() !== "" ? String(v).trim() : null);
  return get(tx?.advisorNo) || get(payload?.advisorNo) || get(cached) || null;
};

/**
 * Cache enum keys for RR session transaction data
 * @type {{txEnvelope: string, JobData: string, SelectedCustomer: string, AdvisorNo: string, VINCandidates: string, SelectedVin: string, ExportResult: string, PendingRO: string}}
 */
const RRCacheEnums = {
  txEnvelope: "RR.txEnvelope",
  JobData: "RR.JobData",
  SelectedCustomer: "RR.SelectedCustomer",
  AdvisorNo: "RR.AdvisorNo",
  VINCandidates: "RR.VINCandidates",
  SelectedVin: "RR.SelectedVin",
  ExportResult: "RR.ExportResult",
  PendingRO: "RR.PendingRO"
};

/**
 * Get transaction type string for job ID
 * @param jobid
 * @returns {`rr:${string}`}
 */
const getTransactionType = (jobid) => `rr:${jobid}`;

/**
 * Default RR TTL (1 hour)
 * @type {number}
 */
const defaultRRTTL = 60 * 60;

/**
 * Resolve RR OpCode from bodyshop.rr_configuration.defaults
 * Is Duplicated on client/src/utils/dmsUtils.js
 * @param bodyshop
 * @returns {string}
 */
const resolveRROpCodeFromBodyshop = (bodyshop) => {
  if (!bodyshop) throw new Error("bodyshop is required");

  const cfg = bodyshop?.rr_configuration || {};
  const defaults = cfg?.defaults || {};

  const prefix = (defaults.prefix ?? "").toString().trim();
  const base = (defaults.base ?? "").toString().trim();
  const suffix = (defaults.suffix ?? "").toString().trim();

  if (!prefix && !base && !suffix) {
    throw new Error("No RR OpCode parts found in bodyshop configuration");
  }

  return `${prefix}${base}${suffix}`;
};

/**
 * Enhanced Early RO labor totals are enabled by default for backwards compatibility.
 * Shops can explicitly set md_functionality_toggles.enhanced_early_ros to false to opt out.
 * @param bodyshop
 * @returns {boolean}
 */
const isEnhancedEarlyROEnabled = (bodyshop) => bodyshop?.md_functionality_toggles?.enhanced_early_ros !== false;

module.exports = {
  RRCacheEnums,
  defaultRRTTL,
  getTransactionType,
  isEnhancedEarlyROEnabled,
  ownersFromVinBlocks,
  makeVehicleSearchPayloadFromJob,
  normalizeCustomerCandidates,
  readAdvisorNo,
  resolveRROpCodeFromBodyshop
};
