/* ===============================
   Audit – ARDEM VITU (run_id-aware)
   - Prefer run_id when available (data-runid on <tr>)
   - Batch ID = run_id (preferred) or MMDDYY-### (fallback)
   - Download URL includes run_id (if present) and selected state (if any)
   - Rejected column uses 'duplicates' when provided by backend, else falls back to failed/rejected
   - Rejected details filtered to run start/end when available
   - If a state is selected and server returns no runs -> show explicit message and stop (NO retry)
   - Defensive escaping and fallbacks
=============================== */

console.debug("[audit.js] batch-id run_id-aware loaded");

/* ---------- Popup helper (Bootstrap modal if available, else alert) ---------- */
(function ensureShowWarning(){
  if (typeof window.showWarning === "function") return; // respect existing definition from HTML
  window.showWarning = function(message) {
    try {
      if (typeof $ === "function" && $("#dateWarningModal").length) {
        $("#dateWarningModal .modal-body").text(message || "Please check your inputs.");
        $("#dateWarningModal").modal("show");
        return;
      }
    } catch (e) { /* noop */ }
    alert(message || "Please check your inputs.");
  };
})();

/* ---------- Small utils ---------- */
function pad2(n){ return String(n).padStart(2,"0"); }
function nonEmpty(v){ return v!==undefined && v!==null && String(v).trim()!==""; }
function toNumber(v, def=0){ const n = Number(v); return Number.isFinite(n) ? n : def; }

function escapeHtml(s) {
  if (s === undefined || s === null) return "";
  return String(s)
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

/* ---------- Date parsing/formatting ---------- */
function isoDateFromInput(s) {
  if (!s) return "";
  const str = String(s).trim();
  const m = str.match(/^(\d{4})-(\d{2})-(\d{2})/);
  if (m) return `${m[1]}-${m[2]}-${m[3]}`;
  if (/^\d{10,13}$/.test(str)) {
    const ms = str.length === 13 ? Number(str) : Number(str) * 1000;
    const d = new Date(ms);
    const yyyy = d.getFullYear();
    const mm = pad2(d.getMonth() + 1);
    const dd = pad2(d.getDate());
    return `${yyyy}-${mm}-${dd}`;
  }
  return "";
}

function displayDateMMDDYYYYFromIso(iso) {
  if (!iso) return "";
  const m = iso.match(/^(\d{4})-(\d{2})-(\d{2})$/);
  if (!m) return "";
  const [, yyyy, mm, dd] = m;
  return `${mm}/${dd}/${yyyy}`;
}

function mmddyyFromIso(iso) {
  if (!iso) return "000000";
  const m = iso.match(/^(\d{4})-(\d{2})-(\d{2})$/);
  if (!m) return "000000";
  const [, yyyy, mm, dd] = m;
  return `${mm}${dd}${yyyy.slice(-2)}`;
}

function extractTime(s) {
  if (!s) return "";
  const str = String(s);
  let m = str.match(/T(\d{2}:\d{2}:\d{2})/);
  if (m) return m[1];
  m = str.match(/\s(\d{2}:\d{2}:\d{2})/);
  if (m) return m[1];
  if (/^\d{10,13}$/.test(str)) {
    const ms = str.length === 13 ? Number(str) : Number(str) * 1000;
    const d = new Date(ms);
    return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
  }
  return "";
}

/* Convert MM/DD/YYYY (or MM-DD-YYYY) -> YYYY-MM-DD */
function isoFromDisplayDate(mmddyyyy) {
  if (!mmddyyyy) return "";
  const s = String(mmddyyyy).trim();
  const m = s.match(/^(\d{2})[\/-](\d{2})[\/-](\d{4})$/);
  if (!m) return "";
  const [, mm, dd, yyyy] = m;
  return `${yyyy}-${mm}-${dd}`;
}

/* ---------- Table rendering ---------- */
function setAuditEmpty(msg){
  const tb = document.getElementById("audit-body");
  if (!tb) return;
  tb.innerHTML = `<tr><td colspan="8" class="text-center text-muted">${escapeHtml(msg)}</td></tr>`;
}

function _getSelectedState() {
  try { return document.getElementById("stateSelect")?.value || ""; } catch { return ""; }
}

function _appendStateParam(href) {
  const st = _getSelectedState();
  if (!st) return href;
  if (href.indexOf('?') === -1) return `${href}?state=${encodeURIComponent(st)}`;
  return `${href}&state=${encodeURIComponent(st)}`;
}

function renderAuditRows(items){
  const tb = document.getElementById("audit-body");
  if (!tb) return;

  if (!Array.isArray(items) || items.length === 0) {
    setAuditEmpty("No runs to display for the selected range.");
    return;
  }

  // Sort by date then start time then run_id for stable sequencing per day
  const sorted = [...items].sort((a, b) => {
    const aStart = a?.started || a?.start || a?.started_at || a?.start_time || a?.startTime || "";
    const bStart = b?.started || b?.start || b?.started_at || b?.start_time || b?.startTime || "";
    const aIso = isoDateFromInput(aStart) || isoDateFromInput(a?.ended) || isoDateFromInput(a?.date) || "";
    const bIso = isoDateFromInput(bStart) || isoDateFromInput(b?.ended) || isoDateFromInput(b?.date) || "";
    const cmpD = String(aIso).localeCompare(String(bIso));
    if (cmpD !== 0) return cmpD;
    if (aStart !== bStart) return String(aStart).localeCompare(String(bStart));
    if (a?.run_id && b?.run_id) return String(a.run_id).localeCompare(String(b.run_id));
    return 0;
  });

  const countersByDate = Object.create(null);

  const rows = sorted.map(r => {
    const startedRaw = r?.started || r?.start || r?.started_at || r?.start_time || r?.startTime || "";
    const endedRaw   = r?.ended   || r?.end   || r?.ended_at   || r?.end_time   || r?.endTime || "";

    const dateISO = isoDateFromInput(startedRaw) || isoDateFromInput(endedRaw) || isoDateFromInput(r?.date) || "";
    const dateDisp = displayDateMMDDYYYYFromIso(dateISO) || "";

    const startTime = extractTime(startedRaw);
    const endTime   = extractTime(endedRaw);

    const runId = r?.run_id ? String(r.run_id) : "";
    let batchId = "";
    if (runId) {
      batchId = runId;
    } else {
      const key = mmddyyFromIso(dateISO);
      if (!countersByDate[key]) countersByDate[key] = 0;
      countersByDate[key] += 1;
      const seq = String(countersByDate[key]).padStart(3,"0");
      batchId = `${key}-${seq}`;
    }

    let rejectedCount = null;
    if (nonEmpty(r?.duplicates)) rejectedCount = toNumber(r.duplicates, 0);
    else if (nonEmpty(r?.rejected)) rejectedCount = toNumber(r.rejected, 0);
    else if (nonEmpty(r?.failed)) rejectedCount = toNumber(r.failed, 0);
    else rejectedCount = 0;

    const rejectedCell = (rejectedCount > 0)
      ? `<a href="#" class="rejected-link" data-count="${rejectedCount}">${escapeHtml(String(rejectedCount))}</a>`
      : `<span>${escapeHtml(String(rejectedCount))}</span>`;

    const dlHrefBase = dateISO ? `/api/audit/download?date=${encodeURIComponent(dateISO)}` : "#";
    const runIdParam = runId ? `&run_id=${encodeURIComponent(runId)}` : "";
    let dlHref = (dlHrefBase === "#") ? "#" : (dlHrefBase + runIdParam);
    dlHref = _appendStateParam(dlHref);
    const disabledAttrs = dlHref === "#" ? ' tabindex="-1" aria-disabled="true"' : "";

    const runIdAttr = runId ? `data-runid="${escapeHtml(runId)}"` : "";
    const trAttrs = `data-dateiso="${escapeHtml(dateISO)}" data-start="${escapeHtml(startedRaw)}" data-end="${escapeHtml(endedRaw)}" ${runIdAttr}`;

    const received  = toNumber(r?.received ?? r?.count ?? 0, 0);
    const processed = toNumber(r?.processed ?? r?.success ?? 0, 0);

    return `
      <tr ${trAttrs}>
        <td>${escapeHtml(dateDisp)}</td>
        <td>${escapeHtml(batchId)}</td>
        <td>${escapeHtml(startTime)}</td>
        <td>${escapeHtml(endTime)}</td>
        <td>${escapeHtml(String(received))}</td>
        <td>${escapeHtml(String(processed))}</td>
        <td>${rejectedCell}</td>
        <td>
          <a href="${dlHref}" class="btn btn-sm btn-success"${disabledAttrs} aria-label="Download Excel for ${escapeHtml(batchId)}">
            <i class="fas fa-file-excel"></i> Download
          </a>
        </td>
      </tr>`;
  });

  tb.innerHTML = rows.join("");
}

/* ---------- Filters (read from UI) ---------- */
function isAuditApplied(){
  try { return !!JSON.parse(localStorage.getItem("auditFiltersApplied") || "{}").applied; }
  catch { return false; }
}
function getUIAuditFilters(){
  return {
    fromDate: document.getElementById("fromDate")?.value || "",
    toDate: document.getElementById("toDate")?.value || "",
    state: document.getElementById("stateSelect")?.value || ""
  };
}
function allAuditFiltersSet(f){ return !!(f.fromDate && f.toDate); }

/* ---------- Helpers for safe client-side and run-level matching ---------- */
const AUDIT_STATE_CANDIDATES = ['state','state_code','stateName','location','region','stateShort'];

function _rowHasStateField(row) {
  if (!row || typeof row !== 'object') return false;
  for (const k of AUDIT_STATE_CANDIDATES) {
    if (Object.prototype.hasOwnProperty.call(row, k)) return true;
    for (const kk of Object.keys(row)) {
      if (kk.toLowerCase() === k.toLowerCase()) return true;
    }
  }
  return false;
}
function _rowMatchesState(row, desiredStateLower) {
  if (!row) return false;
  for (const k of AUDIT_STATE_CANDIDATES) {
    let v = row[k];
    if (v == null) {
      const foundKey = Object.keys(row).find(kk => kk.toLowerCase() === k.toLowerCase());
      if (foundKey) v = row[foundKey];
    }
    if (!v) continue;
    const vs = String(v).trim().toLowerCase();
    if (vs === desiredStateLower) return true;
    if (vs.includes(desiredStateLower)) return true;
    if (desiredStateLower.includes(vs)) return true;
  }
  return false;
}

// Check if a run object already declares association with a state (fast)
function runMatchesStateDirect(run, desiredStateLower) {
  if (!run) return false;
  // direct run-level keys
  const directCandidates = ['state','state_code','stateName','location','region','stateShort'];
  for (const k of directCandidates) {
    let v = run[k];
    if (v == null) {
      const fk = Object.keys(run).find(kk => kk.toLowerCase() === k.toLowerCase());
      if (fk) v = run[fk];
    }
    if (!v) continue;
    const vs = String(v).trim().toLowerCase();
    if (vs === desiredStateLower) return true;
    if (vs.includes(desiredStateLower)) return true;
  }

  // run.states array or run.by_state object
  if (Array.isArray(run.states) && run.states.length) {
    for (const s of run.states) {
      if (!s) continue;
      if (String(s).trim().toLowerCase() === desiredStateLower) return true;
      if (String(s).trim().toLowerCase().includes(desiredStateLower)) return true;
    }
  }
  if (run.by_state && typeof run.by_state === 'object') {
    for (const key of Object.keys(run.by_state || {})) {
      if (!key) continue;
      if (key.toLowerCase() === desiredStateLower) return true;
      if (key.toLowerCase().includes(desiredStateLower)) return true;
    }
  }

  // run.files/rows might be present — check quickly
  const candidateArrays = ['files','rows','items','files_rows','file_rows'];
  for (const arrKey of candidateArrays) {
    const arr = run[arrKey];
    if (Array.isArray(arr) && arr.length) {
      for (const f of arr) {
        if (_rowMatchesState(f, desiredStateLower)) return true;
      }
    }
  }

  return false;
}

/* ---------- Helper: fetch file-rows for a given run (best-effort) ---------- */
/* Tries a few likely endpoints used in your app:
   1) /api/files?date=YYYY-MM-DD&run_id=RUNID
   2) /api/files?run_id=RUNID
   3) /api/files?date=YYYY-MM-DD&filename=... (if run contains sample filename)
   Returns array of file rows or [] on failure.
*/
async function fetchFileRowsForRun(run) {
  if (!run) return [];
  const runId = run.run_id || run.runId || run.batchId || null;
  const dateISO = isoDateFromInput(run.started || run.start || run.date) || isoFromDisplayDate(run.date) || "";
  const sampleFilename = run.filename || run.file || run.sample_filename || run.sampleFilename || null;

  const tryUrls = [];
  if (dateISO && runId) tryUrls.push(`/api/files?date=${encodeURIComponent(dateISO)}&run_id=${encodeURIComponent(runId)}`);
  if (runId) tryUrls.push(`/api/files?run_id=${encodeURIComponent(runId)}`);
  if (dateISO && sampleFilename) tryUrls.push(`/api/files?date=${encodeURIComponent(dateISO)}&filename=${encodeURIComponent(sampleFilename)}`);
  // de-duplicate
  const seen = new Set();
  const urls = tryUrls.filter(u => { if (seen.has(u)) return false; seen.add(u); return true; });

  for (const url of urls) {
    try {
      const res = await fetch(url, { credentials: "include" });
      if (!res.ok) continue;
      const data = await res.json();
      const rows = Array.isArray(data) ? data
                : Array.isArray(data?.results) ? data.results
                : Array.isArray(data?.files) ? data.files
                : Array.isArray(data?.rows) ? data.rows
                : [];
      if (Array.isArray(rows) && rows.length) return rows;
    } catch (ex) {
      console.warn("[audit] file-rows fetch failed for", url, ex);
      // try next
    }
  }
  return [];
}

/* ---------- Fetch + Render (GLOBAL) ---------- */
async function fetchAndRender(){
  const f = getUIAuditFilters();

  // Guard: show popup if only one date is selected
  const onlyOneDate = (!!f.fromDate && !f.toDate) || (!f.fromDate && !!f.toDate);
  if (onlyOneDate) {
    showWarning("Please select both Start and End date.");
    setAuditEmpty("Select a complete date range, then click Apply.");
    return;
  }

  if (!allAuditFiltersSet(f) || !isAuditApplied()) {
    setAuditEmpty("Select a date range, then click Apply.");
    return;
  }

  // Build URL including state if selected. NOTE: no retry without state — explicit behavior.
  const baseParams = new URLSearchParams({ from: f.fromDate, to: f.toDate });
  if (f.state) baseParams.set("state", f.state);
  const urlTry = `/api/audit?${baseParams.toString()}`;

  try {
    console.debug("[audit] fetching ->", urlTry);
    const res = await fetch(urlTry, { credentials: "include" });

    if (!res.ok) {
      throw new Error(`HTTP ${res.status}`);
    }

    const data = await res.json();
    const items = Array.isArray(data) ? data
      : Array.isArray(data?.results) ? data.results
      : Array.isArray(data?.runs)    ? data.runs
      : [];

    // If user selected a state and the server returned no runs, show explicit message and stop.
    if (f.state && Array.isArray(items) && items.length === 0) {
      console.debug("[audit] server returned 0 runs for selected state:", f.state);
      setAuditEmpty(`No runs for selected state (${escapeHtml(f.state)}) in the given date range.`);
      return;
    }

    let finalItems = items;

    // If state selected, ensure only runs belonging to that state are shown.
    if (f.state && Array.isArray(items) && items.length > 0) {
      const desired = String(f.state).trim().toLowerCase();

      // Fast pass: direct run-level matches
      const directMatches = items.filter(run => runMatchesStateDirect(run, desired));
      const toProbe = items.filter(run => !runMatchesStateDirect(run, desired));

      const finalMatches = [...directMatches];

      // Probe uncertain runs for state by fetching file rows for that run (best-effort)
      // To avoid excessive probing for very large responses, cap probes.
      const PROBE_LIMIT = 50;
      let probeList = toProbe;
      if (Array.isArray(toProbe) && toProbe.length > PROBE_LIMIT) {
        probeList = toProbe.slice(0, PROBE_LIMIT);
        console.warn(`[audit] probe list truncated to ${PROBE_LIMIT} runs to avoid excessive network calls (original ${toProbe.length}).`);
      }

      for (const run of probeList) {
        const runId = run.run_id || run.runId || run.batchId || null;
        const sampleFilename = run.filename || run.file || run.sample_filename || run.sampleFilename || null;
        if (!runId && !sampleFilename) continue;

        try {
          const rows = await fetchFileRowsForRun(run);
          if (Array.isArray(rows) && rows.length) {
            const anyMatch = rows.some(r => _rowMatchesState(r, desired));
            if (anyMatch) {
              finalMatches.push(run);
              continue;
            }
          }
        } catch (ex) {
          console.warn("[audit] probe failed for run", runId || sampleFilename, ex);
          // continue
        }
      }

      // If we truncated probes and finalMatches still empty, be explicit — do NOT show the full unfiltered list.
      if (finalMatches.length === 0) {
        console.debug("[audit] after direct & probe checks, no runs matched the selected state:", f.state);
        setAuditEmpty(`No runs for selected state (${escapeHtml(f.state)}) in the given date range.`);
        return;
      }

      finalItems = finalMatches;
    }

    renderAuditRows(finalItems);
  } catch (err) {
    console.error("Audit fetch error:", err);
    setAuditEmpty("Error loading data. Please adjust filters or try again.");
  }
}

/* Expose globally (used by your HTML) */
window.fetchAndRender = fetchAndRender;

/* ---------- Rejected click handler (delegated on tbody) ---------- */
document.addEventListener("DOMContentLoaded", () => {
  const tb = document.getElementById("audit-body");
  if (!tb) return;

  tb.addEventListener("click", async (e) => {
    const link = e.target.closest("a.rejected-link");
    if (!link) return;

    e.preventDefault();
    const count = Number(link.dataset.count || 0);

    // find the row and its data attributes
    const tr = link.closest("tr");
    if (!tr) { showWarning("Could not determine run for rejected details."); return; }

    let dateISO = tr.getAttribute("data-dateiso") || "";
    const runStartRaw = tr.getAttribute("data-start") || "";
    const runEndRaw = tr.getAttribute("data-end") || "";
    const runId = tr.getAttribute("data-runid") || "";

    if (!dateISO) {
      // fallback: parse first cell text
      const dateCell = tr.querySelector("td");
      const dateDisp = dateCell ? dateCell.textContent.trim() : "";
      const isoDateGuess = isoFromDisplayDate(dateDisp) || isoDateFromInput(dateDisp);
      if (isoDateGuess) dateISO = isoDateGuess;
    }

    if (!dateISO) { showWarning("Could not determine date for rejected details."); return; }
    if (count === 0) { showWarning("No rejected details available for this batch."); return; }

    try {
      // include run_id if present and include selected state param if present
      const stateParam = getUIAuditFilters().state ? `&state=${encodeURIComponent(getUIAuditFilters().state)}` : "";
      const url = `/api/audit/rejected?date=${encodeURIComponent(dateISO)}${runId ? `&run_id=${encodeURIComponent(runId)}` : ""}${stateParam}`;
      const res = await fetch(url, { credentials: "include" });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      let rows = await res.json();
      if (!Array.isArray(rows) || rows.length === 0) {
        showWarning("No rejected details found for this date.");
        return;
      }

      // If run window present, further filter client-side
      const runStartMs = runStartRaw ? Date.parse(runStartRaw) : null;
      const runEndMs   = runEndRaw   ? Date.parse(runEndRaw)   : null;
      if (runStartMs && runEndMs && runEndMs >= runStartMs) {
        const filtered = rows.filter(rr => {
          const sMs = rr.started ? Date.parse(rr.started) : null;
          const eMs = rr.ended   ? Date.parse(rr.ended)   : null;
          if (sMs && sMs >= runStartMs && sMs <= runEndMs) return true;
          if (eMs && eMs >= runStartMs && eMs <= runEndMs) return true;
          return false;
        });
        if (filtered.length) rows = filtered;
      }

      if (!rows.length) {
        showWarning("No rejected details found for this run (showing date-level rejects).");
      }

      // Build HTML table for modal
      const cols = ["Filename", "Status", "Rejected For", "Rejection Reason", "S3 Key"];
      const headerHtml = cols.map(c => `<th>${escapeHtml(c)}</th>`).join("");
      const bodyRows = rows.map(r => {
        const filename = escapeHtml(r.filename || "");
        const status = escapeHtml(r.status || "");
        const rejected_for = escapeHtml(r.rejected_for || r.rejectedFor || "");
        let rejection_reason = r.rejection_reason || r.rejectionReason || "";
        if (!rejection_reason && r.rejected_for && String(r.rejected_for).toLowerCase() === "txn_md5" && r.txn_md5) {
          rejection_reason = `Duplicate transaction (txn_md5: ${String(r.txn_md5).slice(0,8)}...)`;
        }
        if (r.txn_md5 && String(rejection_reason || "").toLowerCase().indexOf(String(r.txn_md5).toLowerCase()) === -1) {
          if (String(rejected_for || "").toLowerCase().includes("txn")) {
            rejection_reason = (rejection_reason ? rejection_reason + " — " : "") + `txn_md5: ${String(r.txn_md5).slice(0,8)}...`;
          }
        }
        const rejection_reason_html = escapeHtml(String(rejection_reason || ""));
        const s3_key = escapeHtml(r.s3_key || r.s3Key || "");

        const fileLink = filename ? `<a href="/file/pdf?date=${encodeURIComponent(dateISO)}&filename=${encodeURIComponent(filename)}" target="_blank">${filename}</a>` : "";

        return `<tr>
          <td>${fileLink || filename}</td>
          <td>${status}</td>
          <td>${rejected_for}</td>
          <td>${rejection_reason_html}</td>
          <td>${s3_key}</td>
        </tr>`;
      }).join("");

      const tableHtml = `
        <div class="table-responsive">
          <table class="table table-sm table-bordered">
            <thead><tr>${headerHtml}</tr></thead>
            <tbody>${bodyRows}</tbody>
          </table>
        </div>`;

      const container = document.getElementById("rejectedDetailsBody");
      if (container) container.innerHTML = tableHtml;

      try {
        if (typeof $ === "function" && $("#rejectedDetailsModal").length) {
          $("#rejectedDetailsModal").modal("show");
          return;
        }
      } catch (ex) {}

      // fallback
      showWarning(JSON.stringify(rows, null, 2));
    } catch (err) {
      console.error("Failed to load rejected details:", err);
      showWarning("Failed to load rejected details. Check server logs or try again.");
    }
  });
});
