/**
 * Shared utilities used by all job processors.
 * Avoids duplicating helpers that currently live inside route files.
 */
import axios from "axios";
import PDFDocument from "pdfkit";
import fsSync from "fs";
import { storage } from "../../storage";
import {
  InsertPatient,
  insertPatientSchema,
} from "../../../../../packages/db/types/patient-types";

const SELENIUM_BASE_URL =
  process.env.SELENIUM_AGENT_BASE_URL || "http://localhost:5002";

// ---------------------------------------------------------------------------
// Python service helpers
// ---------------------------------------------------------------------------

/**
 * Call a synchronous Python service endpoint and return its result.
 * The Python service runs the selenium job inline and returns the result directly.
 * Timeout is long (8 min) to accommodate slow selenium flows.
 */
export async function callPythonSync(
  endpoint: string,
  payload: any,
  timeoutMs = 8 * 60 * 1000
): Promise<any> {
  const resp = await axios.post(
    `${SELENIUM_BASE_URL}${endpoint}`,
    payload,
    { timeout: timeoutMs }
  );
  const data = resp.data;
  if (data?.status === "error") {
    const msg =
      typeof data.message === "string"
        ? data.message
        : data.message?.msg ?? "Selenium returned error status";
    throw new Error(msg);
  }
  return data;
}

// ---------------------------------------------------------------------------
// General utilities
// ---------------------------------------------------------------------------

export function sleep(ms: number) {
  return new Promise<void>((r) => setTimeout(r, ms));
}

export function splitName(fullName?: string | null) {
  if (!fullName) return { firstName: "", lastName: "" };
  const parts = fullName.trim().split(/\s+/).filter(Boolean);
  const firstName = parts.shift() ?? "";
  const lastName = parts.join(" ") ?? "";
  return { firstName, lastName };
}

export async function imageToPdfBuffer(imagePath: string): Promise<Buffer> {
  return new Promise<Buffer>((resolve, reject) => {
    try {
      const doc = new PDFDocument({ autoFirstPage: false });
      const chunks: Uint8Array[] = [];
      doc.on("data", (c: any) => chunks.push(c));
      doc.on("end", () => resolve(Buffer.concat(chunks)));
      doc.on("error", reject);

      const A4_W = 595.28;
      const A4_H = 841.89;
      doc.addPage({ size: [A4_W, A4_H] });
      doc.image(imagePath, 0, 0, { fit: [A4_W, A4_H], align: "center", valign: "center" });
      doc.end();
    } catch (e) {
      reject(e);
    }
  });
}

// ---------------------------------------------------------------------------
// Patient DB helpers
// ---------------------------------------------------------------------------

export async function createOrUpdatePatientByInsuranceId(options: {
  insuranceId: string;
  firstName?: string | null;
  lastName?: string | null;
  dob?: string | Date | null;
  userId: number;
}) {
  const { insuranceId, firstName, lastName, dob, userId } = options;
  if (!insuranceId) throw new Error("Missing insuranceId");

  // Normalize insuranceId the same way insertPatientSchema does (strip spaces)
  const normalizedId = insuranceId.replace(/\s+/g, "");

  const incomingFirst = (firstName || "").trim();
  const incomingLast = (lastName || "").trim();

  console.log(`[createOrUpdatePatient] insuranceId="${normalizedId}" firstName="${incomingFirst}" lastName="${incomingLast}" userId=${userId}`);

  // Family members often share the same insuranceId (e.g. Delta Dental family plans).
  // When a DOB is provided, look up by insuranceId+DOB so each family member is a
  // distinct patient record.  If the combo doesn't exist → new patient; never
  // overwrite a different family member who happens to share the same insuranceId.
  let dobDate: Date | null = null;
  if (dob) {
    const parsed = new Date(dob);
    if (!isNaN(parsed.getTime())) dobDate = parsed;
  }

  let patient = null;
  if (dobDate) {
    // Primary lookup: exact insuranceId + DOB match
    patient = await storage.getPatientByInsuranceIdAndDob(normalizedId, dobDate);
    console.log(`[createOrUpdatePatient] id+DOB lookup: ${patient ? `found id=${patient.id}` : "not found"}`);
    if (!patient) {
      // Fallback: patient exists but has no DOB yet — update rather than duplicate
      const byIdOnly = await storage.getPatientByInsuranceId(normalizedId);
      if (byIdOnly && !byIdOnly.dateOfBirth) {
        patient = byIdOnly;
        console.log(`[createOrUpdatePatient] id-only fallback (no DOB on record): found id=${patient.id}`);
      }
    }
  } else {
    // No DOB supplied — fall back to insuranceId-only (legacy / non-family plans)
    patient = await storage.getPatientByInsuranceId(normalizedId);
    console.log(`[createOrUpdatePatient] id-only lookup: ${patient ? `found id=${patient.id}` : "not found"}`);
  }

  if (patient && patient.id) {
    const updates: any = {};
    if (incomingFirst && String(patient.firstName ?? "").trim() !== incomingFirst)
      updates.firstName = incomingFirst;
    if (incomingLast && String(patient.lastName ?? "").trim() !== incomingLast)
      updates.lastName = incomingLast;
    if (dobDate && !patient.dateOfBirth) updates.dateOfBirth = dobDate;
    if (Object.keys(updates).length > 0) {
      console.log(`[createOrUpdatePatient] updating patient id=${patient.id} with`, updates);
      await storage.updatePatient(patient.id, updates);
      patient = dobDate
        ? await storage.getPatientByInsuranceIdAndDob(normalizedId, dobDate)
        : await storage.getPatientByInsuranceId(normalizedId);
    }
    return patient;
  }

  const createPayload: any = {
    firstName: incomingFirst,
    lastName: incomingLast,
    dateOfBirth: dobDate ?? dob,   // use parsed Date; fallback to raw string if dobDate is null
    gender: "",
    phone: "",
    userId,
    insuranceId: normalizedId,
  };

  let patientData: InsertPatient;
  try {
    patientData = insertPatientSchema.parse(createPayload);
  } catch (e1) {
    console.warn(`[createOrUpdatePatient] schema parse failed:`, e1);
    patientData = createPayload as InsertPatient;
  }

  try {
    await storage.createPatient(patientData);
    console.log(`[createOrUpdatePatient] patient created successfully for insuranceId="${normalizedId}"`);
  } catch (dbErr: any) {
    console.error(`[createOrUpdatePatient] DB create failed:`, dbErr?.message ?? dbErr);
    throw dbErr;
  }

  return storage.getPatientByInsuranceId(normalizedId);
}
