import { runPortfolioPdfBatch } from "./ylivitleiyuglkyjgkec.mjs";
import {
  createPortfolioPdfPolicySelectorSet,
  portfolioPdfPolicySelectorMatches,
} from "./abywezhrsiqkqfhujfvq.mjs";
import {
  installGeneraliPolicyHistoryHook,
  pickGeneraliPolicyHistoryRow,
} from "./tnodnwhtpiozxvtuvvlq.mjs";

export const GENERALI_PORTFOLIO_PDF_CONCURRENCY = 1;
export const GENERALI_POLICY_DOCUMENT_TYPES = Object.freeze(["POLICY"]);

const GENERALI_FRONTEND_HOST = "portal.generali.pl";
const GENERALI_FRONTEND_BASE = `https://${GENERALI_FRONTEND_HOST}/frontend/`;
const GENERALI_DOWNLOAD_POLICY_URL = `${GENERALI_FRONTEND_BASE}printoutSender/downloadPolicy`;
const GENERALI_MIN_PDF_BYTES = 1_000;
const GENERALI_MAX_PDF_BYTES = 64 * 1024 * 1024;
const GENERALI_HISTORY_TIMEOUT_MS = 30_000;

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

function normalizedPortfolioKey(value) {
  return String(value ?? "").replace(/[^0-9A-Za-z]/g, "").toUpperCase();
}

function sourcePolicy(sourceEntry) {
  const policy = sourceEntry?.policy ?? sourceEntry;
  return policy && typeof policy === "object" && !Array.isArray(policy)
    ? policy
    : null;
}

function failedGeneraliPdfBatch(
  remaining,
  deferred = 0,
  circuitCode = "unknown",
) {
  return {
    publicResult: {
      attempted: 0,
      succeeded: 0,
      failed: 0,
      backoff: 0,
      quarantined: 0,
      unsupported: 0,
      deferred: Math.max(0, deferred),
      remaining: Math.max(0, remaining),
      batchComplete: false,
      circuitOpen: true,
      circuitCode,
      manualRetryAvailable: false,
    },
    privateResult: {
      manualRetryIdentities: [],
    },
  };
}

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" };
  }
  if (normalizedStatus === 404) {
    return { type: "document_failure", code: "not_found" };
  }
  return { type: "global_failure", code: "unknown" };
}

function decodeSuccessfulPdf(result) {
  const encodedLength = typeof result?.base64 === "string"
    ? result.base64.length
    : 0;
  const expectedEncodedLength = Number.isSafeInteger(result?.size)
    ? Math.ceil(result.size / 3) * 4
    : 0;
  if (
    typeof result?.base64 !== "string"
    || !Number.isSafeInteger(result.size)
    || result.size < GENERALI_MIN_PDF_BYTES
    || result.size > GENERALI_MAX_PDF_BYTES
    || encodedLength === 0
    || encodedLength !== expectedEncodedLength
    || encodedLength > Math.ceil(GENERALI_MAX_PDF_BYTES / 3) * 4
    || !/^(?:[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 < GENERALI_MIN_PDF_BYTES
    || bytes.byteLength > GENERALI_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 };
}

function navigationFailure(error) {
  return error?.name === "TimeoutError"
    ? { type: "global_failure", code: "timeout" }
    : { type: "global_failure", code: "navigation" };
}

function pageHasExpectedPolicyUrl(page, policyId) {
  if (!page || typeof page.url !== "function") return true;
  try {
    const current = new URL(page.url());
    return current.origin === `https://${GENERALI_FRONTEND_HOST}`
      && current.hostname === GENERALI_FRONTEND_HOST
      && current.port === ""
      && current.username === ""
      && current.password === ""
      && current.pathname === `/frontend/policy/id/${encodeURIComponent(policyId)}`;
  } catch {
    return false;
  }
}

 
export async function downloadGeneraliPolicyPdfResult(page, {
  policyId,
  productCode = "",
} = {}) {
  const normalizedPolicyId = compactValue(policyId);
  const normalizedProductCode = String(productCode ?? "").trim();
  if (!normalizedPolicyId) {
    return { type: "unsupported", code: "document_not_available" };
  }
  if (
    normalizedProductCode.length > 256
    || normalizedProductCode.includes("\0")
  ) {
    return { type: "unsupported", code: "document_not_available" };
  }
  if (
    !page
    || typeof page.goto !== "function"
    || typeof page.evaluate !== "function"
  ) {
    return { type: "global_failure", code: "navigation" };
  }

  const detailsUrl = `${GENERALI_FRONTEND_BASE}policy/id/${encodeURIComponent(normalizedPolicyId)}?s=1&code=${encodeURIComponent(normalizedProductCode)}`;
  let navigationResponse;
  try {
    navigationResponse = await page.goto(detailsUrl, {
      waitUntil: "domcontentloaded",
      timeout: 60_000,
    });
  } catch (error) {
    return navigationFailure(error);
  }
  const navigationStatus = typeof navigationResponse?.status === "function"
    ? Number(navigationResponse.status())
    : 200;
  if (navigationStatus !== 200) return classifyHttpFailure(navigationStatus);
  if (!pageHasExpectedPolicyUrl(page, normalizedPolicyId)) {
    return { type: "global_failure", code: "session_expired" };
  }

  const deadline = Date.now() + GENERALI_HISTORY_TIMEOUT_MS;
  let history = null;
  while (Date.now() < deadline) {
    let captured;
    try {
      captured = await page.evaluate(() => window.__bpPolicyHistory || null);
    } catch (error) {
      return navigationFailure(error);
    }
    if (captured && String(captured.policyId || "") === normalizedPolicyId) {
      history = captured;
      break;
    }
    await new Promise((resolve) => globalThis.setTimeout(resolve, 500));
  }
  if (!history) {
    return { type: "global_failure", code: "portal_unavailable" };
  }

  const row = pickGeneraliPolicyHistoryRow(history.policyHistoryRowForms);
  if (
    !row
    || !GENERALI_POLICY_DOCUMENT_TYPES.includes(
      String(row.eventType || "").trim().toUpperCase(),
    )
  ) {
    return { type: "unsupported", code: "document_not_generated" };
  }
  const documentHash = compactValue(row.docHash, 4_096);
  if (!documentHash) {
    return { type: "unsupported", code: "document_not_generated" };
  }

  let result;
  try {
    result = await page.evaluate(async ({
      downloadUrl,
      maxPdfBytes,
      minPdfBytes,
      pid,
      docType,
      docHash,
    }) => {
      const mediaType = (response) => String(
        response?.headers?.get?.("content-type") || "",
      ).split(";", 1)[0].trim().toLowerCase();
      const query = `?policyId=${encodeURIComponent(pid)}&docType=${encodeURIComponent(docType)}&docHash=${encodeURIComponent(docHash)}`;
      const controller = new AbortController();
      const timer = setTimeout(() => controller.abort(), 45_000);
      try {
        let response;
        try {
          response = await fetch(downloadUrl + query, {
            credentials: "include",
            headers: { Accept: "application/pdf" },
            redirect: "manual",
            signal: controller.signal,
          });
        } catch (error) {
          return {
            kind: error?.name === "AbortError" ? "timeout" : "network",
          };
        }
        if (
          response?.redirected
          || response?.type === "opaqueredirect"
          || (Number(response?.status) >= 300 && Number(response?.status) <= 399)
        ) {
          return { kind: "redirected" };
        }
        let responseUrl;
        let expectedUrl;
        try {
          responseUrl = new URL(response?.url || "");
          expectedUrl = new URL(downloadUrl);
        } catch {
          return { kind: "cross_origin" };
        }
        if (
          responseUrl.protocol !== "https:"
          || responseUrl.origin !== expectedUrl.origin
          || responseUrl.pathname !== expectedUrl.pathname
        ) {
          return { kind: "cross_origin" };
        }
        if (response?.status !== 200) {
          return { kind: "http", status: response?.status };
        }
        const type = mediaType(response);
        if (type === "text/html" || type === "application/xhtml+xml") {
          return { kind: "session_content" };
        }
        if (
          type !== "application/pdf"
          && type !== "application/octet-stream"
        ) {
          return { kind: "unexpected_content_type" };
        }

        const declaredLength = String(
          response?.headers?.get?.("content-length") || "",
        ).trim();
        if (declaredLength) {
          if (!/^\d+$/.test(declaredLength)) {
            return { kind: "invalid_response" };
          }
          const parsedLength = Number(declaredLength);
          if (
            !Number.isSafeInteger(parsedLength)
            || parsedLength > maxPdfBytes
          ) {
            return { kind: "invalid_pdf" };
          }
        }

        let bytes;
        try {
          if (typeof response?.body?.getReader === "function") {
            const reader = response.body.getReader();
            const chunks = [];
            let total = 0;
            while (true) {
              const chunkResult = await reader.read();
              if (chunkResult?.done === true) break;
              const chunk = chunkResult?.value;
              if (!(chunk instanceof Uint8Array)) {
                return { kind: "invalid_response" };
              }
              total += chunk.byteLength;
              if (!Number.isSafeInteger(total) || total > maxPdfBytes) {
                await reader.cancel?.().catch(() => {});
                return { kind: "invalid_pdf" };
              }
              chunks.push(chunk);
            }
            bytes = new Uint8Array(total);
            let offset = 0;
            for (const chunk of chunks) {
              bytes.set(chunk, offset);
              offset += chunk.byteLength;
            }
          } else {
            const buffer = await response.arrayBuffer();
            if (
              !buffer
              || !Number.isSafeInteger(buffer.byteLength)
              || buffer.byteLength > maxPdfBytes
            ) {
              return { kind: "invalid_pdf" };
            }
            bytes = new Uint8Array(buffer);
          }
        } catch (error) {
          return {
            kind: error?.name === "AbortError"
              ? "timeout"
              : "network",
          };
        }
        if (
          bytes.byteLength < minPdfBytes
          || bytes[0] !== 0x25
          || bytes[1] !== 0x50
          || bytes[2] !== 0x44
          || bytes[3] !== 0x46
          || bytes[4] !== 0x2d
        ) {
          return { kind: "invalid_pdf" };
        }

        let binary = "";
        try {
          for (let offset = 0; offset < bytes.length; offset += 0x8000) {
            binary += String.fromCharCode.apply(
              null,
              bytes.subarray(offset, offset + 0x8000),
            );
          }
          return {
            kind: "success",
            base64: btoa(binary),
            size: bytes.byteLength,
          };
        } catch {
          return { kind: "download_failure" };
        }
      } finally {
        clearTimeout(timer);
      }
    }, {
      downloadUrl: GENERALI_DOWNLOAD_POLICY_URL,
      maxPdfBytes: GENERALI_MAX_PDF_BYTES,
      minPdfBytes: GENERALI_MIN_PDF_BYTES,
      pid: normalizedPolicyId,
      docType: "POLICY",
      docHash: documentHash,
    });
  } catch (error) {
    return navigationFailure(error);
  }

  if (result?.kind === "success") return decodeSuccessfulPdf(result);
  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 === "cross_origin") {
    return { type: "global_failure", code: "session_expired" };
  }
  if (result?.kind === "http") {
    return classifyHttpFailure(result.status);
  }
  if (
    result?.kind === "unexpected_content_type"
    || result?.kind === "invalid_response"
  ) {
    return { type: "global_failure", code: "portal_unavailable" };
  }
  return { type: "global_failure", code: "unknown" };
}

 
export function prepareGeneraliPortfolioPdfEntries({
  entries,
  policies,
  skipPolicyNumbers = [],
  knownPolicyNumbers = [],
} = {}) {
  if (entries !== undefined && policies !== undefined) {
    throw new TypeError("Podaj entries albo policies, nie oba pola jednoczesnie.");
  }
  const source = entries ?? policies;
  if (!Array.isArray(source)) {
    throw new TypeError("entries albo policies musi byc tablica.");
  }
  const skipSet = createPortfolioPdfPolicySelectorSet(skipPolicyNumbers, {
    insurer: "generali",
  });
  const knownSet = createPortfolioPdfPolicySelectorSet(knownPolicyNumbers, {
    insurer: "generali",
  });
  const eligible = [];
  for (let index = 0; index < source.length; index += 1) {
    const sourceEntry = source[index];
    const policy = sourcePolicy(sourceEntry);
    if (
      !policy
      || String(policy.insurer || "").trim().toUpperCase() !== "GENERALI"
    ) {
      continue;
    }
    const originalPolicyNumber = compactValue(policy.policyNumber);
    const key = normalizedPortfolioKey(originalPolicyNumber);
    const identitySource = { ...policy, policyNumber: originalPolicyNumber };
    if (!key || portfolioPdfPolicySelectorMatches(skipSet, identitySource)) continue;
    eligible.push({
      sourceIndex: index,
      known: portfolioPdfPolicySelectorMatches(knownSet, identitySource),
      runnerEntry: sourceEntry?.policy
        ? { ...sourceEntry, policy, policyNumber: originalPolicyNumber }
        : { policy, policyNumber: originalPolicyNumber },
    });
  }
  eligible.sort((left, right) => (
    Number(left.known) - Number(right.known)
    || left.sourceIndex - right.sourceIndex
  ));
  return eligible.map(({ runnerEntry }) => runnerEntry);
}

export async function runGeneraliPortfolioPdfBatch({
  page,
  retryStore,
  pdfDirectory,
  entries,
  policies,
  limit,
  skipPolicyNumbers = [],
  knownPolicyNumbers = [],
  onProgress = () => {},
} = {}) {
  if (typeof onProgress !== "function") {
    throw new TypeError("onProgress musi byc funkcja.");
  }
  const eligible = prepareGeneraliPortfolioPdfEntries({
    entries,
    policies,
    skipPolicyNumbers,
    knownPolicyNumbers,
  });
  if (
    limit !== undefined
    && (!Number.isSafeInteger(limit) || limit <= 0)
  ) {
    throw new TypeError("limit musi byc dodatnia liczba calkowita.");
  }
  const selected = eligible.slice(0, limit ?? eligible.length);
  const businessDeferred = eligible.length - selected.length;
  const locallyUnsupported = selected.filter((entry) => {
    const rawPolicyNumber = String(
      entry?.policyNumber ?? entry?.policy?.policyNumber ?? "",
    ).trim();
    const normalizedPolicyNumber = normalizedPortfolioKey(rawPolicyNumber);
    return rawPolicyNumber.length > 200
      || !/^[A-Z0-9]{2,64}$/.test(normalizedPolicyNumber);
  });
  const runnable = selected.filter((entry) => (
    !locallyUnsupported.includes(entry)
  ));
  const planned = selected.length;
  if (runnable.length > 0) {
    if (!page || typeof page.evaluateOnNewDocument !== "function") {
      return failedGeneraliPdfBatch(
        eligible.length,
        businessDeferred,
        "navigation",
      );
    }
    try {
      await installGeneraliPolicyHistoryHook(page);
    } catch {
      return failedGeneraliPdfBatch(
        eligible.length,
        businessDeferred,
        "navigation",
      );
    }
  }

  let processed = locallyUnsupported.length;
  if (processed > 0) onProgress({ processed, planned });
  let result;
  try {
    result = await runPortfolioPdfBatch({
      retryStore,
      insurer: "generali",
      pdfDirectory,
      entries: runnable,
      limit: runnable.length || undefined,
      concurrency: GENERALI_PORTFOLIO_PDF_CONCURRENCY,
      fetchDocument: async ({ entry }) => {
        try {
          return await downloadGeneraliPolicyPdfResult(page, {
            policyId: entry.policy?.policyId,
            productCode: entry.policy?.productCode,
          });
        } finally {
          processed += 1;
          onProgress({ processed, planned });
        }
      },
    });
  } catch {
    
    
    return failedGeneraliPdfBatch(
      eligible.length,
      businessDeferred,
      "unknown",
    );
  }
  if (processed < selected.length) {
    processed = selected.length;
    onProgress({ processed, planned });
  }
  const publicResult = {
    ...result.publicResult,
    attempted: result.publicResult.attempted + locallyUnsupported.length,
    unsupported: result.publicResult.unsupported + locallyUnsupported.length,
    deferred: result.publicResult.deferred + businessDeferred,
    remaining: result.publicResult.remaining + businessDeferred,
    batchComplete: result.publicResult.batchComplete === true
      && businessDeferred === 0,
  };
  return {
    ...result,
    publicResult,
  };
}
