const LOG_PREFIX = "[BulkUpload:Parser]";

type ParseLogPayload = Record<string, unknown>;

function log(stage: string, payload?: ParseLogPayload) {
  if (payload) {
    console.log(LOG_PREFIX, stage, payload);
    return;
  }
  console.log(LOG_PREFIX, stage);
}

export function logBulkUploadParseStart(
  mode: "new" | "update",
  fileName: string,
) {
  log("start", { mode, fileName });
}

export function logBulkUploadWorkbook(
  mode: "new" | "update",
  sheetNames: string[],
) {
  log("workbook", { mode, sheetNames, sheetCount: sheetNames.length });
}

export function logBulkUploadSheetParse(
  mode: "new" | "update",
  sheetName: string,
  details: ParseLogPayload,
) {
  log(`sheet:${sheetName}`, { mode, ...details });
}

export function logBulkUploadValidation(
  mode: "new" | "update",
  details: ParseLogPayload,
) {
  log("validation", { mode, ...details });
}

export function logBulkUploadParseSuccess(
  mode: "new" | "update",
  details: ParseLogPayload,
) {
  log("success", { mode, ...details });
}

export function logBulkUploadParseError(
  mode: "new" | "update",
  error: unknown,
  details?: ParseLogPayload,
) {
  log("error", {
    mode,
    message: error instanceof Error ? error.message : String(error),
    ...details,
  });
  if (error instanceof Error && error.stack) {
    console.error(LOG_PREFIX, "stack", error.stack);
  }
}

export function logBulkUploadParseAbort(
  mode: "new" | "update",
  reason: string,
  details?: ParseLogPayload,
) {
  log("abort", { mode, reason, ...details });
}

export type DuplicateRow = {
  Username?: string;
  Email?: string;
  PhoneNumber?: string;
  Position?: string;
  ReportsTo?: string;
  __rowNumber?: number;
};

function normalizeDuplicateKey(
  value: string,
  field: keyof DuplicateRow,
): string {
  const trimmed = value.trim();
  if (field === "PhoneNumber") {
    return trimmed.replace(/\D/g, "");
  }
  if (field === "Email" || field === "Username") {
    return trimmed.toLowerCase();
  }
  return trimmed.toLowerCase();
}

function findDuplicateValues<T extends DuplicateRow>(
  rows: T[],
  field: keyof T,
): Array<{ value: string; count: number; rows: T[] }> {
  const map = new Map<string, T[]>();
  for (const row of rows) {
    const raw = row[field];
    const value = typeof raw === "string" ? raw.trim() : "";
    if (!value) continue;
    const key = normalizeDuplicateKey(value, field as keyof DuplicateRow);
    if (!key) continue;
    if (!map.has(key)) map.set(key, []);
    map.get(key)!.push(row);
  }
  return [...map.entries()]
    .filter(([, matches]) => matches.length > 1)
    .map(([, matches]) => ({
      value: String(matches[0][field] ?? "").trim(),
      count: matches.length,
      rows: matches,
    }));
}

export function collectBulkUploadDuplicateErrors(
  users: DuplicateRow[],
): string[] {
  const errors: string[] = [];
  const checks: Array<{ field: keyof DuplicateRow; label: string }> = [
    { field: "Username", label: "Username" },
    { field: "Email", label: "Email" },
    { field: "PhoneNumber", label: "Phone number" },
  ];

  for (const { field, label } of checks) {
    const duplicates = findDuplicateValues(users, field);
    for (const duplicate of duplicates) {
      const rows = duplicate.rows
        .map((row) =>
          typeof row.__rowNumber === "number"
            ? `row ${row.__rowNumber}`
            : String(row.Username ?? "unknown"),
        )
        .join(", ");
      errors.push(
        `Duplicate ${label} "${duplicate.value}" found in ${rows}`,
      );
    }
  }

  return errors;
}

export function logBulkUploadDuplicateCheck(
  mode: "new" | "update",
  positions: DuplicateRow[],
  users: DuplicateRow[],
) {
  const positionNameDups = findDuplicateValues(positions, "Position");
  const usernameDups = findDuplicateValues(users, "Username");
  const emailDups = findDuplicateValues(users, "Email");
  const phoneDups = findDuplicateValues(users, "PhoneNumber");
  const duplicateErrors = collectBulkUploadDuplicateErrors(users);

  const invalidReportsTo = positions.filter((row) => {
    const position = String(row.Position ?? "").trim();
    const reportsTo = String(row.ReportsTo ?? "").trim();
    return reportsTo && reportsTo === position;
  });

  const userPositions = new Set(
    positions
      .map((row) => String(row.Position ?? "").trim())
      .filter(Boolean),
  );
  const missingPositionRefs = users.filter(
    (row) =>
      row.Position &&
      !userPositions.has(String(row.Position).trim()),
  );

  log("duplicate-check", {
    mode,
    duplicateErrors,
    positionNameDuplicates: positionNameDups.map((item) => ({
      value: item.value,
      count: item.count,
    })),
    usernameDuplicates: usernameDups.map((item) => ({
      value: item.value,
      count: item.count,
      usernames: item.rows.map((row) => row.Username),
    })),
    emailDuplicates: emailDups.map((item) => ({
      value: item.value,
      count: item.count,
      usernames: item.rows.map((row) => row.Username),
    })),
    phoneDuplicates: phoneDups.map((item) => ({
      value: item.value,
      count: item.count,
      usernames: item.rows.map((row) => row.Username),
    })),
    selfReferencingReportsTo: invalidReportsTo.map((row) => row.Position),
    usersWithUnknownPosition: missingPositionRefs.map((row) => ({
      username: row.Username,
      position: row.Position,
    })),
  });
}
