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

export const TRASTI_PORTFOLIO_PDF_CONCURRENCY = 1;
export const TRASTI_PORTAL_ORIGIN = "https://portal.trasti.pl";
export const TRASTI_DOCUMENT_PATH = "/api/salesApi/documentoffer";
export const TRASTI_PDF_TRANSIENT_RETRY_DELAY_MS = 1800;

function delay(milliseconds) {
  return new Promise((resolve) => globalThis.setTimeout(resolve, milliseconds));
}

const TRASTI_MIN_PDF_BYTES = 1_000;
const TRASTI_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;
}

function classifyHttpFailure(status) {
  const normalizedStatus = Number(status);
  
  
  
  if (normalizedStatus === 204) return { type: "document_failure", code: "not_found" };
  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 < TRASTI_MIN_PDF_BYTES
    || result.size > TRASTI_MAX_PDF_BYTES
    || encodedLength === 0
    || encodedLength !== expectedEncodedLength
    || encodedLength > Math.ceil(TRASTI_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 < TRASTI_MIN_PDF_BYTES
    || bytes.byteLength > TRASTI_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 downloadTrastiPolicyPdfResult(page, multiOfferId) {
  const originalMultiOfferId = compactPolicyNumber(multiOfferId);
  const requestPolicyId = normalizedPortfolioKey(originalMultiOfferId);
  if (!requestPolicyId) return { type: "document_failure", code: "not_found" };
  if (!page || typeof page.evaluate !== "function") {
    return { type: "global_failure", code: "navigation" };
  }
  try {
    if (new URL(page.url()).origin !== TRASTI_PORTAL_ORIGIN) {
      return { type: "global_failure", code: "navigation" };
    }
  } catch {
    return { type: "global_failure", code: "navigation" };
  }

  let result;
  try {
    result = await page.evaluate(async ({
      documentPath,
      maxPdfBytes,
      minPdfBytes,
      policyId,
      portalOrigin,
    }) => {
      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 suspiciousBodyKind = (bytes) => {
        const prefixBytes = bytes.subarray(0, Math.min(bytes.byteLength, 512));
        let encoding = "utf-8";
        let offset = 0;
        if (prefixBytes[0] === 0xef && prefixBytes[1] === 0xbb && prefixBytes[2] === 0xbf) {
          offset = 3;
        } else if (prefixBytes[0] === 0xff && prefixBytes[1] === 0xfe) {
          encoding = "utf-16le";
          offset = 2;
        } else if (prefixBytes[0] === 0xfe && prefixBytes[1] === 0xff) {
          const swapped = new Uint8Array(Math.max(0, prefixBytes.byteLength - 2));
          for (let index = 2; index + 1 < prefixBytes.byteLength; index += 2) {
            swapped[index - 2] = prefixBytes[index + 1];
            swapped[index - 1] = prefixBytes[index];
          }
          const text = new TextDecoder("utf-16le").decode(swapped)
            .replace(/^\uFEFF/, "")
            .trimStart()
            .toLowerCase();
          if (text.startsWith("<!doctype") || text.startsWith("<html")) return "html";
          if (text.startsWith("{") || text.startsWith("[")) return "json";
          return "";
        }
        const text = new TextDecoder(encoding).decode(prefixBytes.subarray(offset))
          .replace(/^\uFEFF/, "")
          .trimStart()
          .toLowerCase();
        if (text.startsWith("<!doctype") || text.startsWith("<html")) return "html";
        if (text.startsWith("{") || text.startsWith("[")) return "json";
        return "";
      };
      const tokenKey = Object.keys(localStorage).find((key) => key.includes("-accesstoken-"));
      let token = "";
      try {
        token = JSON.parse(localStorage.getItem(tokenKey) || "{}").secret || "";
      } catch {
      }
      if (!token) return { kind: "session_expired" };

      const controller = new AbortController();
      const timer = setTimeout(() => controller.abort(), 45_000);
      try {
        const response = await fetch(
          `${portalOrigin}${documentPath}?multiOfferId=${encodeURIComponent(policyId)}`,
          {
            method: "GET",
            credentials: "include",
            headers: {
              Accept: "application/pdf, application/octet-stream",
              Authorization: `Bearer ${token}`,
            },
            redirect: "error",
            signal: controller.signal,
          },
        );
        if (response?.redirected) return { kind: "session_expired" };
        let responseUrl;
        try {
          responseUrl = new URL(response?.url || "");
        } catch {
          return { kind: "cross_origin" };
        }
        if (
          responseUrl.origin !== portalOrigin
          || responseUrl.pathname !== documentPath
        ) {
          return { kind: "cross_origin" };
        }
        if (response?.status !== 200) return { kind: "http", status: response?.status };
        const type = mediaType(response);
        if (isHtmlMediaType(type)) return { kind: "session_expired" };
        if (isJsonMediaType(type)) return { kind: "unexpected_content_type" };
        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)) 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 {
          const buffer = await response.arrayBuffer();
          if (!buffer || buffer.byteLength > maxPdfBytes) return { kind: "invalid_pdf" };
          bytes = new Uint8Array(buffer);
        }
        const suspicious = suspiciousBodyKind(bytes);
        if (suspicious === "html") return { kind: "session_expired" };
        if (suspicious === "json") 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" };
        }
        let binary = "";
        const chunkSize = 0x8000;
        for (let offset = 0; offset < bytes.length; offset += chunkSize) {
          binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
        }
        return {
          kind: "success",
          base64: btoa(binary),
          size: bytes.byteLength,
        };
      } catch (error) {
        return { kind: error?.name === "AbortError" ? "timeout" : "network" };
      } finally {
        clearTimeout(timer);
      }
    }, {
      documentPath: TRASTI_DOCUMENT_PATH,
      maxPdfBytes: TRASTI_MAX_PDF_BYTES,
      minPdfBytes: TRASTI_MIN_PDF_BYTES,
      policyId: requestPolicyId,
      portalOrigin: TRASTI_PORTAL_ORIGIN,
    });
  } catch {
    return { type: "global_failure", code: "navigation" };
  }
  try {
    if (new URL(page.url()).origin !== TRASTI_PORTAL_ORIGIN) {
      return { type: "global_failure", code: "session_expired" };
    }
  } catch {
    return { type: "global_failure", code: "session_expired" };
  }

  if (!result || typeof result !== "object" || Array.isArray(result)) {
    return { type: "global_failure", code: "unknown" };
  }
  if (result.kind === "success") return decodeSuccessfulPdf(result);
  if (result.kind === "http") return classifyHttpFailure(result.status);
  if (result.kind === "invalid_pdf") return { type: "document_failure", code: "invalid_pdf" };
  if (result.kind === "session_expired") {
    return { type: "global_failure", code: "session_expired" };
  }
  if (result.kind === "cross_origin") {
    return { type: "global_failure", code: "session_expired" };
  }
  if (result.kind === "timeout") return { type: "global_failure", code: "timeout" };
  if (result.kind === "network") return { type: "global_failure", code: "network" };
  if (result.kind === "unexpected_content_type") {
    return { type: "global_failure", code: "portal_unavailable" };
  }
  if (result.kind === "invalid_response") {
    return { type: "document_failure", code: "download_failed" };
  }
  return { type: "global_failure", code: "unknown" };
}

function failedTrastiPdfBatch(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: [],
    },
  };
}

export function prepareTrastiPortfolioPdfEntries({
  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: "trasti",
  });
  const knownSet = createPortfolioPdfPolicySelectorSet(knownPolicyNumbers, {
    insurer: "trasti",
  });
  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() !== "TRASTI") continue;
    const originalPolicyNumber = compactPolicyNumber(policy.policyNumber);
    const originalMultiOfferId = compactPolicyNumber(policy.multiOfferId);
    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 runTrastiPortfolioPdfBatch({
  page,
  retryStore,
  pdfDirectory,
  entries,
  policies,
  limit,
  skipPolicyNumbers = [],
  knownPolicyNumbers = [],
  onProgress = () => {},
  transientRetryDelayMs = TRASTI_PDF_TRANSIENT_RETRY_DELAY_MS,
} = {}) {
  if (typeof onProgress !== "function") {
    throw new TypeError("onProgress musi byc funkcja.");
  }
  if (!Number.isSafeInteger(transientRetryDelayMs) || transientRetryDelayMs < 0) {
    throw new TypeError("transientRetryDelayMs musi byc nieujemna liczba calkowita.");
  }
  const eligible = prepareTrastiPortfolioPdfEntries({
    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;
  let result;
  try {
    result = await runPortfolioPdfBatch({
      retryStore,
      insurer: "trasti",
      pdfDirectory,
      entries: selected,
      limit: selected.length || undefined,
      concurrency: TRASTI_PORTFOLIO_PDF_CONCURRENCY,
      fetchDocument: async ({ entry }) => {
        try {
          let result = await downloadTrastiPolicyPdfResult(page, entry.policy?.multiOfferId);
          if (result?.type === "global_failure") {
            
            
            await delay(transientRetryDelayMs);
            result = await downloadTrastiPolicyPdfResult(page, entry.policy?.multiOfferId);
          }
          return result;
        } finally {
          processed += 1;
          onProgress({ processed, planned });
        }
      },
    });
  } catch {
    return failedTrastiPdfBatch(eligible.length, businessDeferred);
  }
  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,
    },
  };
}
