import dayjs from "./day";

const EMPTY_VALUE = "<<empty>>";
const NO_CHANGES = "No changes";

const BILL_LINE_KEYS = ["line_desc", "quantity", "actual_price", "actual_cost", "cost_center"];
const JOB_LINE_SKIP_KEYS = new Set(["ah_detail_line", "prt_dsmk_p"]);
const DATE_ONLY_KEYS = new Set(["date"]);
const DATE_TIME_KEYS = new Set(["clockon", "clockoff"]);
const CURRENCY_KEYS = new Set(["actual_price", "actual_cost", "act_price", "db_price", "rate"]);
const HOUR_KEYS = new Set(["productivehrs", "actualhrs", "mod_lb_hrs"]);

const isBlank = (value) => value == null || value === "";

const isStructuredValue = (value) => value != null && typeof value === "object" && !dayjs.isDayjs?.(value);

const formatDate = (value) => (isBlank(value) ? EMPTY_VALUE : dayjs(value).format("YYYY-MM-DD"));

const formatDateTime = (value) => (isBlank(value) ? EMPTY_VALUE : dayjs(value).format("YYYY-MM-DD HH:mm"));

const formatNumber = (value, fractionDigits) =>
  typeof value === "number" ? value.toFixed(fractionDigits) : String(value);

const compareValue = (key, value) => {
  if (isBlank(value)) return EMPTY_VALUE;
  if (DATE_TIME_KEYS.has(key)) return formatDateTime(value);
  if (DATE_ONLY_KEYS.has(key)) return formatDate(value);
  if (dayjs.isDayjs?.(value)) return formatDateTime(value);
  return String(value);
};

const buildFieldChangeDetails = ({ keys, original = {}, updated = {}, displayValue, skippedKeys = new Set() }) =>
  keys
    .filter((key) => key !== "__typename" && !skippedKeys.has(key))
    .filter((key) => !isStructuredValue(original[key]) && !isStructuredValue(updated[key]))
    .map((key) => {
      if (compareValue(key, original[key]) === compareValue(key, updated[key])) return null;
      return `${key}: ${displayValue(key, original[key])} -> ${displayValue(key, updated[key])}`;
    })
    .filter(Boolean);

const formatBillValue = (key, value) => {
  if (isBlank(value)) return EMPTY_VALUE;
  if (DATE_ONLY_KEYS.has(key)) return formatDate(value);
  if (CURRENCY_KEYS.has(key)) return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
  return String(value);
};

const formatJobLineValue = (key, value) => {
  if (isBlank(value)) return EMPTY_VALUE;
  if (CURRENCY_KEYS.has(key)) return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
  if (HOUR_KEYS.has(key)) return formatNumber(value, 1);
  return String(value);
};

const getEmployeeName = (employeeId, employees = [], fallbackEmployee) => {
  if (
    (employeeId == null || fallbackEmployee?.id === employeeId) &&
    (fallbackEmployee?.first_name || fallbackEmployee?.last_name)
  ) {
    return [fallbackEmployee.first_name, fallbackEmployee.last_name].filter(Boolean).join(" ");
  }

  const employee = employees.find(({ id }) => id === employeeId);
  if (employee) {
    return [employee.first_name, employee.last_name].filter(Boolean).join(" ");
  }

  return employeeId ? String(employeeId) : EMPTY_VALUE;
};

const formatTimeTicketValue = (key, value, { employees = [], fallbackEmployee } = {}) => {
  if (isBlank(value)) return EMPTY_VALUE;
  if (key === "employeeid") return getEmployeeName(value, employees, fallbackEmployee);
  if (DATE_TIME_KEYS.has(key)) return formatDateTime(value);
  if (DATE_ONLY_KEYS.has(key)) return formatDate(value);
  if (CURRENCY_KEYS.has(key)) return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
  if (HOUR_KEYS.has(key)) return formatNumber(value, 1);
  if (typeof value === "boolean") return value ? "true" : "false";
  return String(value);
};

const buildBillLineSummary = (line) =>
  BILL_LINE_KEYS.map((key) => `${key}: ${formatBillValue(key, line[key])}`).join(", ");

export function buildBillUpdateAuditDetails({ originalBill = {}, bill = {}, billlines = [] }) {
  const updatedBill = { ...bill, billlines };
  const billKeys = Array.from(new Set([...Object.keys(originalBill), ...Object.keys(updatedBill)])).filter(
    (key) => key !== "billlines"
  );

  const changed = buildFieldChangeDetails({
    keys: billKeys,
    original: originalBill,
    updated: updatedBill,
    displayValue: formatBillValue
  });

  const originalBillLines = originalBill.billlines ?? [];
  const updatedBillLines = updatedBill.billlines ?? [];

  const addedLines = updatedBillLines
    .filter((line) => !line.id)
    .map((line) => `+${line.line_desc || line.description || "new line"} (${buildBillLineSummary(line)})`);

  const removedLines = originalBillLines
    .filter((line) => !updatedBillLines.some((updatedLine) => updatedLine.id === line.id))
    .map(
      (line) => `-${line.line_desc || line.description || line.id || "removed line"} (${buildBillLineSummary(line)})`
    );

  const modifiedLines = updatedBillLines
    .filter((line) => line.id)
    .flatMap((line) => {
      const originalLine = originalBillLines.find(({ id }) => id === line.id);
      if (!originalLine) return [];

      const lineChanges = buildFieldChangeDetails({
        keys: BILL_LINE_KEYS,
        original: originalLine,
        updated: line,
        displayValue: formatBillValue
      });

      if (!lineChanges.length) return [];

      return [`${line.line_desc || line.description || line.id}: ${lineChanges.join("; ")}`];
    });

  if (addedLines.length) changed.push(`billlines added: ${addedLines.join(" | ")}`);
  if (removedLines.length) changed.push(`billlines removed: ${removedLines.join(" | ")}`);
  if (modifiedLines.length) changed.push(`billlines modified: ${modifiedLines.join(" | ")}`);

  return changed.length ? changed.join("; ") : NO_CHANGES;
}

export function buildJobLineInsertAuditDetails(values = {}) {
  const details = Object.entries(values)
    .filter(([key, value]) => !JOB_LINE_SKIP_KEYS.has(key) && !isBlank(value))
    .map(([key, value]) => `${key}: ${formatJobLineValue(key, value)}`);

  return details.length ? details.join("; ") : NO_CHANGES;
}

export function buildJobLineUpdateAuditDetails({ originalLine = {}, values = {} }) {
  const details = buildFieldChangeDetails({
    keys: Object.keys(values),
    original: originalLine,
    updated: values,
    displayValue: formatJobLineValue,
    skippedKeys: JOB_LINE_SKIP_KEYS
  });

  return details.length ? details.join("; ") : NO_CHANGES;
}

export function buildTimeTicketAuditSummary({ originalTicket = {}, submittedValues = {}, employees = [] }) {
  const normalizedOriginal = {
    ...originalTicket,
    jobid: originalTicket.job?.id ?? originalTicket.jobid ?? null
  };

  const details = buildFieldChangeDetails({
    keys: Object.keys(submittedValues),
    original: normalizedOriginal,
    updated: submittedValues,
    displayValue: (key, value) =>
      formatTimeTicketValue(key, value, {
        employees,
        fallbackEmployee: key === "employeeid" ? normalizedOriginal.employee : null
      })
  });

  const employeeName = getEmployeeName(
    submittedValues.employeeid ?? normalizedOriginal.employeeid,
    employees,
    normalizedOriginal.employee
  );

  return {
    date: formatDate(submittedValues.date ?? normalizedOriginal.date),
    details: details.length ? details.join("; ") : NO_CHANGES,
    employeeName,
    jobid: submittedValues.jobid ?? normalizedOriginal.jobid ?? null
  };
}
