



const ERGO_MIN_PDF_BYTES = 1000;
const ERGO_MAX_PDF_BYTES = 64 * 1024 * 1024;
const ERGO_FETCH_TIMEOUT_MS = 45_000;
const ERGO_PRINT_HOSTS = new Set([
  "iagent.ergohestia.pl",
  "polisaonline.ergohestia.pl",
]);

function compactPolicyNumber(value) {
  const normalized = String(value ?? "").trim();
  if (!normalized || normalized.length > 256 || normalized.includes("\0")) {
    return "";
  }
  return normalized;
}

function normalizeHost(value) {
  const normalized = String(value ?? "").trim().toLowerCase();
  if (
    !normalized
    || normalized.length > 253
    || normalized.includes("\0")
    || !/^[a-z0-9.-]+$/.test(normalized)
    || normalized.startsWith(".")
    || normalized.endsWith(".")
    || normalized.includes("..")
  ) {
    return "";
  }
  const labels = normalized.split(".");
  if (
    labels.length < 2
    || labels.some((label) => (
      !label
      || label.length > 63
      || label.startsWith("-")
      || label.endsWith("-")
    ))
  ) {
    return "";
  }
  return ERGO_PRINT_HOSTS.has(normalized) ? normalized : "";
}

function normalizeSymbols(value) {
  if (!Array.isArray(value) || value.length === 0 || value.length > 32) {
    return [];
  }
  const normalized = [];
  const seen = new Set();
  for (const item of value) {
    if (typeof item !== "string") return [];
    const symbol = item.trim();
    if (
      !symbol
      || symbol.length > 128
      || symbol.includes("\0")
      || !/^[A-Za-z0-9._-]+$/.test(symbol)
    ) {
      return [];
    }
    if (!seen.has(symbol)) {
      seen.add(symbol);
      normalized.push(symbol);
    }
  }
  return normalized;
}

function classifyHttpFailure(status) {
  const normalizedStatus = Number(status);
  if (normalizedStatus === 401) {
    return { type: "global_failure", code: "session_expired" };
  }
  if (normalizedStatus === 403) {
    return { type: "global_failure", code: "authorization" };
  }
  if (normalizedStatus === 408 || normalizedStatus === 504) {
    return { type: "global_failure", code: "timeout" };
  }
  if (normalizedStatus === 429) {
    return { type: "global_failure", code: "rate_limited" };
  }
  if (normalizedStatus >= 500 && normalizedStatus <= 599) {
    return { type: "global_failure", code: "portal_unavailable" };
  }
  return { type: "global_failure", code: "unknown" };
}

function decodeSuccessfulPdf(result) {
  const maximumBase64Length = Math.ceil(ERGO_MAX_PDF_BYTES / 3) * 4;
  if (
    typeof result?.base64 !== "string"
    || !Number.isSafeInteger(result.size)
    || result.size < ERGO_MIN_PDF_BYTES
    || result.size > ERGO_MAX_PDF_BYTES
    || result.base64.length !== Math.ceil(result.size / 3) * 4
    || result.base64.length > maximumBase64Length
    || result.base64.length % 4 !== 0
    || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(result.base64)
  ) {
    return { type: "document_failure", code: "invalid_pdf" };
  }
  let bytes;
  try {
    bytes = Buffer.from(result.base64, "base64");
  } catch {
    return { type: "document_failure", code: "invalid_pdf" };
  }
  if (
    bytes.byteLength !== result.size
    || bytes.byteLength < ERGO_MIN_PDF_BYTES
    || bytes.byteLength > ERGO_MAX_PDF_BYTES
    || bytes.toString("base64") !== result.base64
    || bytes[0] !== 0x25
    || bytes[1] !== 0x50
    || bytes[2] !== 0x44
    || bytes[3] !== 0x46
    || bytes[4] !== 0x2d
  ) {
    return { type: "document_failure", code: "invalid_pdf" };
  }
  return { type: "success", bytes };
}

 
export async function downloadErgoPolicyPdfResult({
  page,
  host,
  policyNumber,
  symbols,
} = {}) {
  const normalizedPolicyNumber = compactPolicyNumber(policyNumber);
  if (!normalizedPolicyNumber) {
    return { type: "document_failure", code: "not_found" };
  }
  const normalizedHost = normalizeHost(host);
  const normalizedSymbols = normalizeSymbols(symbols);
  if (!normalizedHost || normalizedSymbols.length === 0) {
    return { type: "global_failure", code: "unknown" };
  }
  if (!page || typeof page.evaluate !== "function") {
    return { type: "global_failure", code: "navigation" };
  }

  let result;
  try {
    result = await page.evaluate(async ({
      fetchTimeoutMs,
      host: requestHost,
      maxPdfBytes,
      minPdfBytes,
      policyNumber: number,
      symbols: symbolList,
    }) => {
      const mediaType = (response) => String(
        response?.headers?.get?.("content-type") || "",
      ).split(";", 1)[0].trim().toLowerCase();
      const isJsonMediaType = (value) => value === "application/json"
        || (
          value.includes("/")
          && /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+\+json$/.test(value)
        );
      const isHtmlMediaType = (value) => (
        value === "text/html" || value === "application/xhtml+xml"
      );
      const classifyBodyPrefix = (buffer) => {
        
        
        
        const prefixBytes = new Uint8Array(buffer);
        let offset = 0;
        const skipAsciiWhitespace = () => {
          while (
            offset < prefixBytes.length
            && (
              prefixBytes[offset] === 0x09
              || prefixBytes[offset] === 0x0a
              || prefixBytes[offset] === 0x0b
              || prefixBytes[offset] === 0x0c
              || prefixBytes[offset] === 0x0d
              || prefixBytes[offset] === 0x20
            )
          ) {
            offset += 1;
          }
        };
        skipAsciiWhitespace();
        let utf16Endian = "";
        if (
          prefixBytes[offset] === 0xef
          && prefixBytes[offset + 1] === 0xbb
          && prefixBytes[offset + 2] === 0xbf
        ) {
          offset += 3;
          skipAsciiWhitespace();
        } else if (
          prefixBytes[offset] === 0xff
          && prefixBytes[offset + 1] === 0xfe
        ) {
          offset += 2;
          utf16Endian = "little";
        } else if (
          prefixBytes[offset] === 0xfe
          && prefixBytes[offset + 1] === 0xff
        ) {
          offset += 2;
          utf16Endian = "big";
        }
        let prefix = "";
        try {
          if (utf16Endian) {
            const codeUnits = [];
            for (
              let index = offset;
              index + 1 < prefixBytes.length && codeUnits.length < 512;
              index += 2
            ) {
              codeUnits.push(utf16Endian === "little"
                ? prefixBytes[index] | (prefixBytes[index + 1] << 8)
                : (prefixBytes[index] << 8) | prefixBytes[index + 1]);
            }
            prefix = String.fromCharCode.apply(null, codeUnits)
              .trimStart()
              .toLowerCase();
          } else {
            prefix = String.fromCharCode.apply(
              null,
              prefixBytes.subarray(
                offset,
                Math.min(prefixBytes.length, offset + 512),
              ),
            ).toLowerCase();
          }
        } catch {
          return "download_failure";
        }
        
        
        
        if (prefix.startsWith("<")) {
          return "session_content";
        }
        if (prefix.startsWith("{") || prefix.startsWith("[")) {
          return "unexpected_content_type";
        }
        return "";
      };
      const toBase64 = (buffer) => {
        const bytes = new Uint8Array(buffer);
        let binary = "";
        for (let offset = 0; offset < bytes.length; offset += 0x8000) {
          binary += String.fromCharCode.apply(
            null,
            bytes.subarray(offset, offset + 0x8000),
          );
        }
        return btoa(binary);
      };
      const readBodyWithinLimit = async (response) => {
        let rawContentLength = null;
        try {
          rawContentLength = response?.headers?.get?.("content-length");
        } catch {
          return { kind: "unexpected_content_type" };
        }
        if (rawContentLength !== null && rawContentLength !== "") {
          const contentLength = String(rawContentLength).trim();
          if (!/^\d+$/.test(contentLength)) {
            return { kind: "unexpected_content_type" };
          }
          const declaredBytes = Number(contentLength);
          if (!Number.isSafeInteger(declaredBytes)) {
            return { kind: "unexpected_content_type" };
          }
          if (declaredBytes > maxPdfBytes) {
            return { kind: "invalid_pdf" };
          }
        }

        if (response?.body && typeof response.body.getReader === "function") {
          let reader;
          try {
            reader = response.body.getReader();
          } catch {
            return { kind: "network" };
          }
          const chunks = [];
          let totalBytes = 0;
          while (true) {
            let step;
            try {
              step = await reader.read();
            } catch (error) {
              return {
                kind: error?.name === "AbortError" ? "timeout" : "network",
              };
            }
            if (!step || typeof step !== "object") {
              return { kind: "network" };
            }
            if (step.done) break;
            const value = step.value;
            let chunk;
            if (value instanceof Uint8Array) {
              chunk = value;
            } else if (value instanceof ArrayBuffer) {
              chunk = new Uint8Array(value);
            } else {
              return { kind: "network" };
            }
            if (chunk.byteLength > maxPdfBytes - totalBytes) {
              try {
                const cancellation = reader.cancel();
                cancellation?.catch?.(() => {});
              } catch {
              }
              return { kind: "invalid_pdf" };
            }
            totalBytes += chunk.byteLength;
            chunks.push(chunk);
          }
          const buffer = new ArrayBuffer(totalBytes);
          const target = new Uint8Array(buffer);
          let offset = 0;
          for (const chunk of chunks) {
            target.set(chunk, offset);
            offset += chunk.byteLength;
          }
          return { kind: "body", buffer };
        }

        let buffer;
        try {
          buffer = await response.arrayBuffer();
        } catch (error) {
          return {
            kind: error?.name === "AbortError" ? "timeout" : "network",
          };
        }
        if (
          !buffer
          || !(buffer instanceof ArrayBuffer)
          || !Number.isSafeInteger(buffer.byteLength)
          || buffer.byteLength < 0
        ) {
          return { kind: "network" };
        }
        
        
        if (buffer.byteLength > maxPdfBytes) {
          return { kind: "invalid_pdf" };
        }
        return { kind: "body", buffer };
      };
      const fetchOne = async (symbol) => {
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), fetchTimeoutMs);
        try {
          const response = await fetch(
            `https://${requestHost}/api/printouts/v1/policies/${encodeURIComponent(number)}/symbols/${encodeURIComponent(symbol)}`,
            {
              credentials: "include",
              headers: {
                Accept: "application/pdf",
                "X-Requested-With": "XMLHttpRequest",
              },
              signal: controller.signal,
            },
          );
          if (response?.redirected) return { kind: "redirected" };
          const status = Number(response?.status);
          if (status !== 200) {
            return status === 404
              ? { kind: "not_found" }
              : { kind: "http", status };
          }

          const type = mediaType(response);
          if (isHtmlMediaType(type)) return { kind: "session_content" };
          if (
            isJsonMediaType(type)
            || (
              type
              && type !== "application/pdf"
              && type !== "application/octet-stream"
            )
          ) {
            return { kind: "unexpected_content_type" };
          }

          const body = await readBodyWithinLimit(response);
          if (body.kind !== "body") return body;
          const prefixKind = classifyBodyPrefix(body.buffer);
          if (prefixKind) return { kind: prefixKind };
          if (body.buffer.byteLength < minPdfBytes) {
            return { kind: "invalid_pdf" };
          }
          const bytes = new Uint8Array(body.buffer);
          if (!(
            bytes[0] === 0x25
            && bytes[1] === 0x50
            && bytes[2] === 0x44
            && bytes[3] === 0x46
            && bytes[4] === 0x2d
          )) {
            return { kind: "invalid_pdf" };
          }
          try {
            return {
              kind: "success",
              base64: toBase64(body.buffer),
              size: bytes.byteLength,
            };
          } catch {
            return { kind: "download_failure" };
          }
        } catch (error) {
          return {
            kind: error?.name === "AbortError" ? "timeout" : "network",
          };
        } finally {
          clearTimeout(timer);
        }
      };

      let sawInvalidPdf = false;
      for (const symbol of symbolList) {
        const fetched = await fetchOne(symbol);
        if (fetched.kind === "not_found") continue;
        if (fetched.kind === "invalid_pdf") {
          sawInvalidPdf = true;
          continue;
        }
        return fetched;
      }
      return { kind: sawInvalidPdf ? "invalid_pdf" : "not_found" };
    }, {
      fetchTimeoutMs: ERGO_FETCH_TIMEOUT_MS,
      host: normalizedHost,
      maxPdfBytes: ERGO_MAX_PDF_BYTES,
      minPdfBytes: ERGO_MIN_PDF_BYTES,
      policyNumber: normalizedPolicyNumber,
      symbols: normalizedSymbols,
    });
  } catch {
    return { type: "global_failure", code: "navigation" };
  }

  if (result?.kind === "success") return decodeSuccessfulPdf(result);
  if (result?.kind === "not_found") {
    return { type: "document_failure", code: "not_found" };
  }
  if (result?.kind === "invalid_pdf") {
    return { type: "document_failure", code: "invalid_pdf" };
  }
  if (result?.kind === "download_failure") {
    return { type: "document_failure", code: "download_failed" };
  }
  if (result?.kind === "network") {
    return { type: "global_failure", code: "network" };
  }
  if (result?.kind === "timeout") {
    return { type: "global_failure", code: "timeout" };
  }
  if (result?.kind === "redirected" || result?.kind === "session_content") {
    return { type: "global_failure", code: "session_expired" };
  }
  if (result?.kind === "unexpected_content_type") {
    return { type: "global_failure", code: "portal_unavailable" };
  }
  if (result?.kind === "http") return classifyHttpFailure(result.status);
  return { type: "global_failure", code: "unknown" };
}
