import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";

function normalizeInsurerId(insurer) {
  const normalized = String(insurer || "").trim().toLowerCase();
  if (!/^[a-z0-9-]{2,40}$/.test(normalized)) throw new Error("Nieprawidłowy identyfikator towarzystwa.");
  return normalized;
}

const SAFE_CONTAINER_KEYS = new Set([
  "recording",
  "frames",
  "controls",
  "tables",
  "position",
  "candidates",
  "rows",
  "cells",
  "fields",
  "options",
]);
const SAFE_ENUM_KEYS = new Set([
  "kind",
  "tag",
  "type",
  "role",
]);
const SAFE_CLASS_KEYS = new Set([
  "className",
  "containerClass",
  "markerClasses",
]);
const SAFE_NUMBER_KEYS = new Set([
  "x",
  "y",
  "width",
  "height",
  "rowCount",
  "frameCount",
  "controlCount",
  "fieldCount",
  "candidateCount",
  "fontSizePx",
  "index",
]);
const SAFE_BOOLEAN_KEYS = new Set([
  "truncated",
  "disabled",
  "readOnly",
  "required",
  "invalid",
  "hasCheckbox",
  "checkboxChecked",
  "hasSelect",
  "hasAmount",
]);
const DENIED_DIAGNOSTICS_KEYS = new Set([
  "value",
  "currentvalue",
  "password",
  "passwd",
  "secret",
  "token",
  "cookie",
  "authorization",
  "headers",
  "request",
  "response",
  "body",
  "storage",
  "text",
  "rowcontext",
  "samplerowtexts",
  "label",
  "title",
  "heading",
  "url",
  "href",
  "name",
  "address",
  "email",
  "phone",
  "pesel",
  "regon",
  "nip",
  "vin",
  "registration",
  "policy",
  "policynumber",
  "client",
  "clientname",
]);

function normalizeDiagnosticsKey(key) {
  return String(key || "").replace(/[^a-z0-9]/gi, "").toLowerCase();
}

function sanitizeFrameHost(value) {
  const rawValue = String(value || "").trim();
  if (!rawValue) return "";
  try {
    const parsed = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(rawValue) ? rawValue : `https://${rawValue}`);
    return /^[a-z0-9.-]{1,253}$/i.test(parsed.hostname) ? parsed.hostname.toLowerCase() : "";
  } catch {
    return "";
  }
}

function sanitizeKnownString(key, value) {
  const text = String(value || "").trim();
  if (key === "capturedAt") {
    return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(text) ? text : "";
  }
  if (key === "reason") {
    return /^[a-z0-9-]{1,80}$/i.test(text) ? text.toLowerCase() : "";
  }
  if (key === "frameHint") return sanitizeFrameHost(text);
  if (SAFE_ENUM_KEYS.has(key)) {
    return /^[a-z0-9_-]{0,40}$/i.test(text) ? text.toLowerCase() : "";
  }
  if (SAFE_CLASS_KEYS.has(key)) {
    return /^[a-z0-9_:\-\s]{0,240}$/i.test(text) ? text.slice(0, 240) : "";
  }
  return "";
}

function isDeniedDiagnosticsKey(key) {
  return DENIED_DIAGNOSTICS_KEYS.has(normalizeDiagnosticsKey(key));
}

export function sanitizeTrainingDiagnostics(value, depth = 0, parentKey = "recording") {
  if (depth > 10 || value == null) return null;
  if (typeof value === "string") {
    const safeString = sanitizeKnownString(parentKey, value);
    return safeString || undefined;
  }
  if (typeof value === "boolean") return SAFE_BOOLEAN_KEYS.has(parentKey) ? value : undefined;
  if (typeof value === "number") {
    return SAFE_NUMBER_KEYS.has(parentKey) && Number.isFinite(value) ? value : undefined;
  }
  if (Array.isArray(value)) {
    if (!SAFE_CONTAINER_KEYS.has(parentKey) && !SAFE_CLASS_KEYS.has(parentKey)) return [];
    return value
      .slice(0, 700)
      .map((item) => sanitizeTrainingDiagnostics(item, depth + 1, parentKey))
      .filter((item) => item !== undefined);
  }
  if (typeof value !== "object") return undefined;
  const sanitized = {};
  for (const [key, item] of Object.entries(value).slice(0, 250)) {
    if (isDeniedDiagnosticsKey(key)) continue;
    if (typeof item === "boolean" && SAFE_BOOLEAN_KEYS.has(key)) {
      sanitized[key] = item;
      continue;
    }
    if (typeof item === "number" && SAFE_NUMBER_KEYS.has(key) && Number.isFinite(item)) {
      sanitized[key] = item;
      continue;
    }
    if (typeof item === "string") {
      const safeString = sanitizeKnownString(key, item);
      if (safeString) sanitized[key] = safeString;
      continue;
    }
    if ((Array.isArray(item) || (item && typeof item === "object")) && (
      SAFE_CONTAINER_KEYS.has(key) || SAFE_CLASS_KEYS.has(key)
    )) {
      sanitized[key] = sanitizeTrainingDiagnostics(item, depth + 1, key);
    }
  }
  return sanitized;
}

export function createTrainingDiagnosticsStore({ storageDirectory }) {
  async function save(insurer, diagnostics) {
    const normalizedInsurer = normalizeInsurerId(insurer);
    await mkdir(storageDirectory, { recursive: true });
    const filePath = join(storageDirectory, `${normalizedInsurer}-ostatnie-nagranie.json`);
    const payload = {
      version: 1,
      insurer: normalizedInsurer,
      savedAt: new Date().toISOString(),
      privacy: "Plik zawiera wyłącznie jawnie dozwolone dane strukturalne; wartości formularzy i tekst strony są odrzucane.",
      recording: sanitizeTrainingDiagnostics(diagnostics && typeof diagnostics === "object" ? diagnostics : {}),
    };
    await writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
    return filePath;
  }

  return { save };
}
