/**
 * Client-side CGM coverage calculator — mirrors python-backend/core/coverage_calculator.py.
 * Used as fallback when the Signal API is unreachable.
 *
 * PHI CONTRACT: This module receives only patient_id, device_type, shipment_date,
 * quantity, payer, component. No names, SSNs, DOBs, or contact fields.
 */

export const FLAG = {
  SUPPLY_LAPSED:      "SUPPLY_LAPSED",
  VISIT_REQUIRED:     "VISIT_REQUIRED",
  TRANSFER_PENDING:   "TRANSFER_PENDING",
  RENEWAL_CRITICAL:   "RENEWAL_CRITICAL",
  RENEWAL_ELEVATED:   "RENEWAL_ELEVATED",
  RENEWAL_SOON:       "RENEWAL_SOON",
  RESUPPLY_READY:     "RESUPPLY_READY",
  ACTIVE:             "ACTIVE",
  NO_RECENT_SHIPMENT: "NO_RECENT_SHIPMENT",
};

// Mirrors FLAG_LABELS in python-backend/api/main.py — keep in sync
const FLAG_LABELS = {
  [FLAG.SUPPLY_LAPSED]:      "Docs Required",
  [FLAG.VISIT_REQUIRED]:     "Escalate",
  [FLAG.TRANSFER_PENDING]:   "New Patient",
  [FLAG.RENEWAL_CRITICAL]:   "Escalate",
  [FLAG.RENEWAL_ELEVATED]:   "Confirm Appointment",
  [FLAG.RENEWAL_SOON]:       "Begin Outreach",
  [FLAG.RESUPPLY_READY]:     "Clear to Ship",
  [FLAG.ACTIVE]:             "On Track",
  [FLAG.NO_RECENT_SHIPMENT]: "No Recent Shipment",
};

const FLAG_ACTIONS = {
  [FLAG.SUPPLY_LAPSED]:      "Contact Prescriber",
  [FLAG.VISIT_REQUIRED]:     "Schedule Visit",
  [FLAG.TRANSFER_PENDING]:   "Obtain full documentation set (SWO, PECOS, PA, diagnosis) — Signal cannot assess status until records are on file",
  [FLAG.RENEWAL_CRITICAL]:   "Request Renewal",
  [FLAG.RENEWAL_ELEVATED]:   "Request Renewal",
  [FLAG.RENEWAL_SOON]:       "Plan Renewal",
  [FLAG.RESUPPLY_READY]:     "Initiate Resupply",
  [FLAG.ACTIVE]:             "No action needed",
  [FLAG.NO_RECENT_SHIPMENT]: "Verify status with prescriber office — confirm patient is still active",
};

/** Wear-day rules — mirrors payer_rules.json */
const DEVICE_RULES = {
  dexcom_g7: { display: "Dexcom G7", sensor: 10 },
  dexcom_g6: { display: "Dexcom G6", sensor: 10, transmitter: 90 },
  freestyle_libre_2: { display: "FreeStyle Libre 2", sensor: 14 },
  freestyle_libre_3: { display: "FreeStyle Libre 3", sensor: 14 },
  omnipod_5: { display: "Omnipod 5", pod: 3, sensor: 14 },
};

export function getDeviceDisplay(deviceType) {
  return DEVICE_RULES[deviceType]?.display ?? deviceType;
}

export function getFlagLabel(flag) {
  return FLAG_LABELS[flag] ?? flag;
}

export function getFlagAction(flag) {
  return FLAG_ACTIONS[flag] ?? "Review";
}

function today() {
  const d = new Date();
  d.setHours(0, 0, 0, 0);
  return d;
}

function daysDiff(a, b) {
  return Math.round((a.getTime() - b.getTime()) / 86400000);
}

function addDays(dateStr, days) {
  const d = new Date(dateStr + "T00:00:00");
  d.setDate(d.getDate() + days);
  return d;
}

function getWearDays(deviceType, component) {
  const device = DEVICE_RULES[deviceType];
  if (!device) return null;
  return device[component] ?? null;
}

// Mirrors payer_rules.json — every payer type currently uses a 180-day visit
// renewal and 30-day resupply window. Keep in sync with the backend config.
function getPayerConfig() {
  return { visitRenewalDays: 180, refillWindowDays: 30 };
}

// Mirrors _compute_priority in python-backend/core/coverage_calculator.py — keep in sync
function computePriority(flag, daysUntilVisit) {
  const urgencyDays = daysUntilVisit !== null && daysUntilVisit !== undefined
    ? Math.abs(daysUntilVisit)
    : 0;
  if (flag === FLAG.SUPPLY_LAPSED) return 2000 + urgencyDays;
  if (flag === FLAG.TRANSFER_PENDING) return 1800;
  if (flag === FLAG.VISIT_REQUIRED) return 1500 + urgencyDays;
  if (flag === FLAG.RENEWAL_CRITICAL) return 1000 + (45 - Math.max(0, urgencyDays));
  if (flag === FLAG.RENEWAL_ELEVATED) return 700 + (60 - Math.max(0, urgencyDays));
  if (flag === FLAG.RENEWAL_SOON) return 400 + (90 - Math.max(0, urgencyDays));
  if (flag === FLAG.RESUPPLY_READY) return 200;
  if (flag === FLAG.NO_RECENT_SHIPMENT) return 5;
  return 0;
}

/**
 * Calculate coverage for a single record.
 * @param {{ patient_id, device_type, shipment_date, quantity, payer, component }} record
 * @returns {{ flag, daysUntilEnd, daysUntilVisit, priority }} or null on error
 */
export function calculateCoverage(record) {
  // Transferred patients: all docs need verification before anything else
  const transferFrom = record.transfer_from || record.csv_transfer_from || record.previous_supplier;
  if (transferFrom && String(transferFrom).trim()) {
    return {
      ...record,
      flag: FLAG.TRANSFER_PENDING,
      daysUntilEnd: 0,
      daysUntilVisit: null,
      priority: computePriority(FLAG.TRANSFER_PENDING, null),
      coverageEndDate: record.shipment_date,
    };
  }

  const wearDays = getWearDays(record.device_type, record.component);
  if (wearDays === null) return null;

  const now = today();
  const totalWear = wearDays * parseInt(record.quantity, 10);
  const coverageEnd = addDays(record.shipment_date, totalWear);
  const daysUntilEnd = daysDiff(coverageEnd, now);

  const payerCfg = getPayerConfig();

  // Visit date resolution mirrors the backend: explicit visit date column when
  // present, otherwise estimated as shipment date minus 30 days.
  const rawVisitDate = record.visit_date || record.csv_visit_date || record.qualifying_visit_date;
  const visitDate = rawVisitDate
    ? new Date(rawVisitDate + "T00:00:00")
    : addDays(record.shipment_date, -30);
  const visitDateValid = !isNaN(visitDate.getTime());

  // Stale-record check FIRST — must precede supply-lapsed, since a 12-month-old
  // shipment always reads as lapsed. A visit date within 12 months overrides.
  const NO_RECENT_SHIPMENT_DAYS = 365;
  const shipmentDate = new Date(record.shipment_date + "T00:00:00");
  const daysSinceShipment = daysDiff(now, shipmentDate);
  const visitWithin12m =
    Boolean(rawVisitDate) && visitDateValid &&
    daysDiff(now, visitDate) < NO_RECENT_SHIPMENT_DAYS;
  if (daysSinceShipment >= NO_RECENT_SHIPMENT_DAYS && !visitWithin12m) {
    return {
      ...record,
      flag: FLAG.NO_RECENT_SHIPMENT,
      daysUntilEnd,
      daysUntilVisit: null,
      priority: computePriority(FLAG.NO_RECENT_SHIPMENT, null),
      coverageEndDate: coverageEnd.toISOString().slice(0, 10),
    };
  }

  let daysUntilVisit = null;
  if (payerCfg.visitRenewalDays && visitDateValid) {
    const visitDue = new Date(visitDate.getTime() + payerCfg.visitRenewalDays * 86400000);
    daysUntilVisit = daysDiff(visitDue, now);
  }

  let flag;
  if (daysUntilEnd < 0) {
    flag = FLAG.SUPPLY_LAPSED;
  } else if (daysUntilVisit !== null && daysUntilVisit < 0) {
    flag = FLAG.VISIT_REQUIRED;
  } else if (daysUntilVisit !== null && daysUntilVisit <= 45) {
    flag = FLAG.RENEWAL_CRITICAL;
  } else if (daysUntilVisit !== null && daysUntilVisit <= 60) {
    flag = FLAG.RENEWAL_ELEVATED;
  } else if (daysUntilVisit !== null && daysUntilVisit <= 90) {
    flag = FLAG.RENEWAL_SOON;
  } else if (daysUntilEnd <= payerCfg.refillWindowDays) {
    flag = FLAG.RESUPPLY_READY;
  } else {
    flag = FLAG.ACTIVE;
  }

  return {
    ...record,
    flag,
    daysUntilEnd,
    daysUntilVisit,
    priority: computePriority(flag, daysUntilVisit),
    coverageEndDate: coverageEnd.toISOString().slice(0, 10),
  };
}

/**
 * Parse raw CSV text into record objects.
 */
export function parseCSV(text) {
  const lines = text.trim().split(/\r?\n/);
  if (lines.length < 2) return [];
  const headers = lines[0].split(",").map((h) => h.trim().toLowerCase());
  return lines
    .slice(1)
    .filter((l) => l.trim())
    .map((line) => {
      const cols = line.split(",").map((c) => c.trim());
      const row = {};
      headers.forEach((h, i) => (row[h] = cols[i] || ""));
      return row;
    });
}

/**
 * Process a batch of parsed CSV rows and return sorted results + skip list.
 */
export function processBatch(rows) {
  const results = [];
  const skipped = [];
  for (const row of rows) {
    const result = calculateCoverage(row);
    if (result) {
      results.push(result);
    } else {
      skipped.push(row.patient_id || "?");
    }
  }
  results.sort((a, b) => b.priority - a.priority);
  return { results, skipped };
}