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



export const PZU_PORTFOLIO_PDF_CONCURRENCY = 1;

const PZU_DOCUMENTS_SEARCH_PATH = "/pc/everest/api/v1/documents/search";
const PZU_MIN_PDF_BYTES = 1_000;
const PZU_MAX_PDF_BYTES = 64 * 1024 * 1024;

function compactPolicyNumber(value) {
  const normalized = String(value ?? "").trim();
  if (!normalized || normalized.length > 256 || 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;
}

 
export function preparePzuPortfolioPdfEntries({
  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: "pzu",
  });
  const knownSet = createPortfolioPdfPolicySelectorSet(knownPolicyNumbers, {
    insurer: "pzu",
  });
  const eligible = [];
  for (let index = 0; index < source.length; index += 1) {
    const sourceEntry = source[index];
    const policy = sourcePolicy(sourceEntry);
    if (!policy) continue;
    const originalPolicyNumber = compactPolicyNumber(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);
}

function classifyHttpFailure(status, phase) {
  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 (phase === "download" && 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 < PZU_MIN_PDF_BYTES
    || result.size > PZU_MAX_PDF_BYTES
    || encodedLength === 0
    || encodedLength !== expectedEncodedLength
    || encodedLength > Math.ceil(PZU_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 < PZU_MIN_PDF_BYTES
    || bytes.byteLength > PZU_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 downloadPzuPolicyPdfResult(page, policyNumber) {
  const normalizedPolicyNumber = compactPolicyNumber(policyNumber);
  if (!normalizedPolicyNumber) {
    return { type: "document_failure", code: "not_found" };
  }
  if (!page || typeof page.evaluate !== "function") {
    return { type: "global_failure", code: "navigation" };
  }

  let result;
  try {
    result = await page.evaluate(async ({
      documentsSearchPath,
      maxPdfBytes,
      minPdfBytes,
      number,
    }) => {
      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 fetchFailureKind = (error) => (
        error?.name === "AbortError" ? "timeout" : "network"
      );
      const fetchWithTimeout = async (url, options, consumeResponse) => {
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), 45_000);
        try {
          const response = await fetch(url, {
            ...options,
            signal: controller.signal,
          });
          return typeof consumeResponse === "function"
            ? await consumeResponse(response)
            : { kind: "response", response };
        } catch (error) {
          return { kind: fetchFailureKind(error) };
        } finally {
          clearTimeout(timer);
        }
      };
      const responseGuard = (response, expectedType, phase) => {
        if (response?.redirected) return { kind: "redirected" };
        if (response?.status !== 200) {
          return { kind: "http", status: response?.status, phase };
        }
        const type = mediaType(response);
        if (isHtmlMediaType(type)) return { kind: "session_content" };
        if (expectedType === "json") {
          return isJsonMediaType(type)
            ? null
            : { kind: "unexpected_content_type" };
        }
        if (isJsonMediaType(type)) return { kind: "unexpected_content_type" };
        if (type !== "application/pdf" && type !== "application/octet-stream") {
          return { kind: "unexpected_content_type" };
        }
        return null;
      };
      const readBinaryWithLimit = async (response) => {
        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)) {
            return { kind: "invalid_response" };
          }
          if (parsedLength > maxPdfBytes) return { kind: "invalid_pdf" };
        }

        let bytes;
        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 {
          let buffer;
          try {
            buffer = await response.arrayBuffer();
          } catch (error) {
            throw error;
          }
          if (
            !buffer
            || !Number.isSafeInteger(buffer.byteLength)
            || buffer.byteLength > maxPdfBytes
          ) {
            return { kind: "invalid_pdf" };
          }
          bytes = new Uint8Array(buffer);
        }

        if (bytes.byteLength === 0) return { kind: "empty_content" };
        let prefixOffset = 0;
        if (
          bytes[0] === 0xef
          && bytes[1] === 0xbb
          && bytes[2] === 0xbf
        ) {
          prefixOffset = 3;
        }
        while (
          prefixOffset < bytes.byteLength
          && (
            bytes[prefixOffset] === 0x09
            || bytes[prefixOffset] === 0x0a
            || bytes[prefixOffset] === 0x0b
            || bytes[prefixOffset] === 0x0c
            || bytes[prefixOffset] === 0x0d
            || bytes[prefixOffset] === 0x20
          )
        ) {
          prefixOffset += 1;
        }
        if (prefixOffset >= bytes.byteLength) {
          return { kind: "empty_content" };
        }
        if (bytes[prefixOffset] === 0x3c) {
          return { kind: "session_content" };
        }
        if (
          bytes[prefixOffset] === 0x7b
          || bytes[prefixOffset] === 0x5b
        ) {
          return { kind: "unexpected_content_type" };
        }
        if (
          bytes.byteLength < minPdfBytes
          || bytes[0] !== 0x25
          || bytes[1] !== 0x50
          || bytes[2] !== 0x44
          || bytes[3] !== 0x46
          || bytes[4] !== 0x2d
        ) {
          return { kind: "invalid_pdf" };
        }
        return { kind: "binary", bytes };
      };

      
      
      const fetchDocumentsSearch = () => fetchWithTimeout(documentsSearchPath, {
        method: "POST",
        credentials: "include",
        headers: {
          "Content-Type": "application/json",
          Accept: "application/json",
        },
        body: JSON.stringify({ criteria: { policyNumber: number } }),
      }, async (response) => {
        const guarded = responseGuard(response, "json", "preflight");
        if (guarded) return guarded;
        let data;
        try {
          data = await response.json();
        } catch {
          return { kind: "invalid_response" };
        }
        return { kind: "documents", data };
      });
      const isSearchServerError = (result) => result?.kind === "http"
        && Number(result.status) >= 500
        && Number(result.status) <= 599;
      let preflight = await fetchDocumentsSearch();
      if (isSearchServerError(preflight)) {
        
        
        await new Promise((resolve) => setTimeout(resolve, 1800));
        preflight = await fetchDocumentsSearch();
      }
      let documentsData = null;
      if (preflight.kind === "documents") {
        documentsData = preflight.data;
      } else if (!isSearchServerError(preflight)) {
        return preflight;
      }
      
      
      
      if (documentsData) {
        if (!Array.isArray(documentsData.results)) {
          return { kind: "schema_mismatch" };
        }
        
        
        
        
        
        const completeEntries = documentsData.results.filter((entry) => (
          entry
          && typeof entry === "object"
          && !Array.isArray(entry)
          && typeof entry.hcpFile === "string"
          && typeof entry.deliveryType === "string"
        ));
        const hasPolicyDocument = completeEntries.some((entry) => (
          entry.hcpFile.startsWith("hcp-file:dsp-Polisa")
          && entry.deliveryType === "FILE"
        ));
        
        
        
        if (!hasPolicyDocument && completeEntries.length === documentsData.results.length) {
          return { kind: "document_not_generated" };
        }
      }

      const postForm = async (overrides, expectedType = "json") => {
        const form = document.querySelector("#gw-root-form");
        if (
          !form
          || typeof form.action !== "string"
          || !form.action.trim()
        ) {
          return { kind: "missing_form" };
        }
        let params;
        try {
          const formData = new FormData(form);
          params = new URLSearchParams();
          for (const [key, value] of formData.entries()) {
            if (typeof value === "string") params.append(key, value);
          }
          for (const [key, value] of Object.entries(overrides)) {
            params.set(key, value);
          }
        } catch {
          return { kind: "missing_form" };
        }
        const fetched = await fetchWithTimeout(form.action, {
          method: "POST",
          credentials: "include",
          headers: {
            "Content-Type": "application/x-www-form-urlencoded",
            Accept: expectedType === "binary"
              ? "application/pdf"
              : "application/json",
          },
          body: params.toString(),
        }, async (response) => {
          const guarded = responseGuard(
            response,
            expectedType === "binary" ? "binary" : "json",
            expectedType === "binary" ? "download" : "pcf",
          );
          if (guarded) return guarded;
          if (expectedType === "binary") {
            const binaryResult = await readBinaryWithLimit(response);
            if (binaryResult.kind !== "binary") return binaryResult;
            const { bytes } = binaryResult;
            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" };
            }
          }
          let data;
          try {
            data = await response.json();
          } catch {
            return { kind: "invalid_response" };
          }
          if (!data || !Array.isArray(data.targets)) {
            return { kind: "schema_mismatch" };
          }
          return { kind: "targets", targets: data.targets };
        });
        return fetched;
      };

      const findViewLink = (targets) => {
        let sawDocumentsGrid = false;
        for (const target of targets || []) {
          if (
            !target
            || typeof target !== "object"
            || typeof target.content !== "string"
            || !target.content.includes("ResultCxPzuLV")
          ) {
            continue;
          }
          sawDocumentsGrid = true;
          let parsed;
          try {
            parsed = new DOMParser().parseFromString(target.content, "text/html");
          } catch {
            return { kind: "schema_mismatch" };
          }
          const rows = [...parsed.querySelectorAll("tr")].filter((row) => (
            typeof row.id === "string" && row.id.includes("ResultCxPzuLV")
          ));
          for (const row of rows) {
            const elements = [...row.querySelectorAll("[id]")];
            const typeCell = elements.find((element) => (
              typeof element.id === "string" && /-Type($|_)/.test(element.id)
            ));
            if (
              !typeCell
              || !/Polisa ubezpieczeniowa/i.test(typeCell.textContent || "")
            ) {
              continue;
            }
            const viewLink = elements.find((element) => (
              typeof element.id === "string"
              && /DocumentsLV_ViewLink$/.test(element.id)
            ));
            if (
              viewLink
              && typeof viewLink.id === "string"
              && viewLink.id.length <= 4_096
              && !viewLink.id.includes("\0")
            ) {
              return { kind: "found", id: viewLink.id };
            }
          }
        }
        return {
          kind: sawDocumentsGrid ? "document_not_found" : "schema_mismatch",
        };
      };

      
      
      
      
      
      
      const findRelatedToSelect = (targets) => {
        for (const target of targets || []) {
          if (
            !target
            || typeof target.content !== "string"
            || !target.content.includes("-RelatedTo")
          ) {
            continue;
          }
          let parsed;
          try {
            parsed = new DOMParser().parseFromString(target.content, "text/html");
          } catch {
            continue;
          }
          const select = [...parsed.querySelectorAll("select")].find((element) => (
            typeof element.name === "string"
            && element.name.endsWith("-RelatedTo")
            && element.name.length <= 4_096
            && !element.name.includes("\0")
          ));
          if (!select) continue;
          const optionValues = [...select.options].map((option) => String(option.value || ""));
          const policyOption = optionValues.find((value) => /^Policy:[\w-]{1,64}$/.test(value));
          const termOptions = optionValues.filter((value) => /^PolicyTerm:[\w-]{1,64}$/.test(value));
          if (policyOption || termOptions.length > 0) {
            return { name: select.name, policyOption: policyOption || null, termOptions };
          }
        }
        return null;
      };

      const opened = await postForm({
        "TabBar-PolicyTab-PolicyTab_PolicyRetrievalItem": number,
        eventSource: "TabBar-PolicyTab-PolicyTab_PolicyRetrievalItem_Button_act",
      });
      if (opened.kind !== "targets") return opened;
      await new Promise((resolve) => setTimeout(resolve, 400));

      let documents = await postForm({
        eventSource: "PolicyFile-MenuLinks-PolicyFile_PolicyFile_Documents_act",
      });
      if (documents.kind !== "targets") return documents;
      let viewLink = findViewLink(documents.targets);
      if (viewLink.kind !== "found") {
        const related = findRelatedToSelect(documents.targets);
        if (related) {
          
          
          
          const searchScopes = related.policyOption
            ? [related.policyOption]
            : related.termOptions.slice().reverse().slice(0, 4);
          const searchEvent = `${related.name.replace(/RelatedTo$/, "SearchAndResetInputSet-SearchLinksInputSet-Search")}_act`;
          for (const scope of searchScopes) {
            const changed = await postForm({
              [related.name]: scope,
              eventSource: `${related.name}_act`,
            });
            if (changed.kind !== "targets") return changed;
            await new Promise((resolve) => setTimeout(resolve, 400));
            const searched = await postForm({
              [related.name]: scope,
              eventSource: searchEvent,
            });
            if (searched.kind !== "targets") return searched;
            viewLink = findViewLink(searched.targets);
            if (viewLink.kind === "found") break;
          }
        } else {
          
          
          const summary = await postForm({
            eventSource: "PolicyFile-MenuLinks-PolicyFile_PolicyFile_Summary_act",
          });
          if (summary.kind !== "targets") return summary;
          await new Promise((resolve) => setTimeout(resolve, 400));
          documents = await postForm({
            eventSource: "PolicyFile-MenuLinks-PolicyFile_PolicyFile_Documents_act",
          });
          if (documents.kind !== "targets") return documents;
          viewLink = findViewLink(documents.targets);
        }
      }
      if (viewLink.kind !== "found") return viewLink;

      return postForm({
        eventSource: `${viewLink.id}_act`,
        downloadRequestId: `dl-${Math.random().toString(36).slice(2)}`,
      }, "binary");
    }, {
      documentsSearchPath: PZU_DOCUMENTS_SEARCH_PATH,
      maxPdfBytes: PZU_MAX_PDF_BYTES,
      minPdfBytes: PZU_MIN_PDF_BYTES,
      number: normalizedPolicyNumber,
    });
  } catch {
    return { type: "global_failure", code: "navigation" };
  }

  if (result?.kind === "success") return decodeSuccessfulPdf(result);
  if (result?.kind === "document_not_generated") {
    return { type: "unsupported", code: "document_not_generated" };
  }
  if (result?.kind === "document_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 === "http") {
    return classifyHttpFailure(result.status, result.phase);
  }
  if (
    result?.kind === "invalid_response"
    || result?.kind === "unexpected_content_type"
    || result?.kind === "empty_content"
  ) {
    return { type: "global_failure", code: "portal_unavailable" };
  }
  if (result?.kind === "missing_form") {
    return { type: "global_failure", code: "navigation" };
  }
  if (result?.kind === "schema_mismatch") {
    
    
    
    
    return { type: "global_failure", code: "portal_unavailable" };
  }
  return { type: "global_failure", code: "unknown" };
}

 
export async function runPzuPortfolioPdfBatch({
  page,
  retryStore,
  pdfDirectory,
  entries,
  policies,
  limit,
  skipPolicyNumbers = [],
  knownPolicyNumbers = [],
  onProgress = () => {},
} = {}) {
  if (typeof onProgress !== "function") {
    throw new TypeError("onProgress musi byc funkcja.");
  }
  const eligible = preparePzuPortfolioPdfEntries({
    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 planned = selected.length;
  let processed = 0;
  const result = await runPortfolioPdfBatch({
    retryStore,
    insurer: "PZU",
    pdfDirectory,
    entries: selected,
    limit: selected.length || undefined,
    concurrency: PZU_PORTFOLIO_PDF_CONCURRENCY,
    fetchDocument: async ({ entry }) => {
      try {
        
        return await downloadPzuPolicyPdfResult(
          page,
          entry.policy?.policyNumber,
        );
      } finally {
        processed += 1;
        onProgress({ processed, planned });
      }
    },
  });
  if (processed < planned) {
    processed = planned;
    onProgress({ processed, planned });
  }
  if (businessDeferred === 0) return result;
  return {
    ...result,
    publicResult: {
      ...result.publicResult,
      deferred: result.publicResult.deferred + businessDeferred,
      remaining: result.publicResult.remaining + businessDeferred,
      batchComplete: false,
    },
  };
}
