import { randomUUID } from "node:crypto";
import {
  lstat,
  mkdir,
  open,
  realpath,
  rename,
  rm,
} from "node:fs/promises";
import {
  basename,
  dirname,
  isAbsolute,
  join,
  relative,
  resolve,
} from "node:path";
import {
  createPortfolioPdfIdentity,
} from "./ldpwrbpjqvuxzylkmjve.mjs";

const DOCUMENT_FAILURE_CODES = new Set([
  "download_failed",
  "invalid_pdf",
  "not_found",
]);
const UNSUPPORTED_CODES = new Set([
  "unsupported_product",
  "unsupported_portal",
  "document_not_available",
  "document_not_generated",
  "not_applicable",
]);
const IMMEDIATE_GLOBAL_CODES = new Set([
  "authentication",
  "authorization",
  "session_expired",
  "rate_limited",
  "write_failed",
  "store_failed",
  "unknown",
]);
const DELAYED_GLOBAL_CODES = new Set([
  "network",
  "timeout",
  "portal_unavailable",
  "navigation",
]);
const GLOBAL_CODE_ALIASES = Object.freeze({
  auth: "authentication",
  session: "session_expired",
  rate: "rate_limited",
  write: "write_failed",
  store: "store_failed",
  portal: "portal_unavailable",
});
const ALL_GLOBAL_CODES = new Set([
  ...IMMEDIATE_GLOBAL_CODES,
  ...DELAYED_GLOBAL_CODES,
]);
const DEFAULT_MAX_PDF_BYTES = 64 * 1024 * 1024;
const DEFAULT_MIN_PDF_BYTES = 8;
const DEFAULT_MAX_BATCH_SIZE = 10_000;
const DEFAULT_CONCURRENCY = 2;

function normalizedCode(value) {
  return String(value ?? "")
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "_")
    .replace(/^_+|_+$/g, "")
    .slice(0, 64);
}

function normalizeDocumentFailureCode(value) {
  const code = normalizedCode(value);
  return DOCUMENT_FAILURE_CODES.has(code) ? code : null;
}

function normalizeUnsupportedCode(value) {
  const code = normalizedCode(value);
  return UNSUPPORTED_CODES.has(code) ? code : null;
}

function normalizeGlobalCode(value) {
  const rawCode = normalizedCode(value);
  const code = GLOBAL_CODE_ALIASES[rawCode] || rawCode;
  return ALL_GLOBAL_CODES.has(code) ? code : "unknown";
}

function positiveInteger(value, fallback, name) {
  const candidate = value === undefined ? fallback : value;
  if (!Number.isSafeInteger(candidate) || candidate <= 0) {
    throw new TypeError(`${name} musi byc dodatnia liczba calkowita.`);
  }
  return candidate;
}

function isPathInside(basePath, candidatePath) {
  const difference = relative(basePath, candidatePath);
  return difference === ""
    || (!difference.startsWith("..\\")
      && !difference.startsWith("../")
      && difference !== ".."
      && !isAbsolute(difference));
}

function safeWriteError() {
  const error = new Error("Nie udalo sie bezpiecznie zapisac dokumentu PDF.");
  error.code = "write_failed";
  return error;
}

function abortError() {
  const error = new Error("Zapis dokumentu PDF zostal anulowany.");
  error.name = "AbortError";
  error.code = "ABORT_ERR";
  return error;
}

function throwIfAborted(signal) {
  if (signal?.aborted) throw abortError();
}

function createIdentity(entry) {
  return createPortfolioPdfIdentity({
    insurer: entry?.insurer,
    policyNumber: entry?.policyNumber,
    validFrom: entry?.validFrom ?? entry?.policy?.validFrom,
    validUntil: entry?.validUntil ?? entry?.policy?.validUntil,
  });
}

function validatePdfBytes(value, { minPdfBytes, maxPdfBytes }) {
  let bytes;
  if (Buffer.isBuffer(value)) {
    bytes = value;
  } else if (value instanceof Uint8Array) {
    bytes = Buffer.from(value.buffer, value.byteOffset, value.byteLength);
  } else {
    return null;
  }
  if (bytes.byteLength < minPdfBytes || bytes.byteLength > maxPdfBytes) return null;
  if (
    bytes[0] !== 0x25
    || bytes[1] !== 0x50
    || bytes[2] !== 0x44
    || bytes[3] !== 0x46
    || bytes[4] !== 0x2d
  ) {
    return null;
  }
  
  
  return Buffer.from(bytes);
}

async function syncDirectory(path, fileOperations) {
  try {
    const handle = await fileOperations.open(path, "r");
    try {
      await handle.sync();
    } finally {
      await handle.close();
    }
  } catch {
    
    
  }
}

async function ensureSafeDirectoryTree({
  configuredBasePath,
  targetDirectory,
  fileOperations,
  createMissing,
}) {
  if (!isPathInside(configuredBasePath, targetDirectory)) throw safeWriteError();
  const rootInfo = await fileOperations.lstat(configuredBasePath);
  if (rootInfo.isSymbolicLink() || !rootInfo.isDirectory()) throw safeWriteError();
  const canonicalBasePath = await fileOperations.realpath(configuredBasePath);

  const difference = relative(configuredBasePath, targetDirectory);
  const segments = difference ? difference.split(/[\\/]+/).filter(Boolean) : [];
  let currentPath = configuredBasePath;
  for (const segment of segments) {
    currentPath = join(currentPath, segment);
    let info;
    try {
      info = await fileOperations.lstat(currentPath);
    } catch (error) {
      if (error?.code !== "ENOENT" || !createMissing) throw error;
      await fileOperations.mkdir(currentPath);
      info = await fileOperations.lstat(currentPath);
    }
    if (info.isSymbolicLink() || !info.isDirectory()) throw safeWriteError();
    const canonicalCurrentPath = await fileOperations.realpath(currentPath);
    if (!isPathInside(canonicalBasePath, canonicalCurrentPath)) throw safeWriteError();
  }
  const canonicalTargetDirectory = await fileOperations.realpath(targetDirectory);
  if (!isPathInside(canonicalBasePath, canonicalTargetDirectory)) throw safeWriteError();
  return { canonicalBasePath, canonicalTargetDirectory };
}

export function createAtomicPortfolioPdfDocumentWriter({
  allowedBaseDir,
  minPdfBytes = DEFAULT_MIN_PDF_BYTES,
  maxPdfBytes = DEFAULT_MAX_PDF_BYTES,
  fileOperations = {
    lstat,
    mkdir,
    open,
    realpath,
    rename,
    rm,
  },
} = {}) {
  const rawBasePath = typeof allowedBaseDir === "string" ? allowedBaseDir.trim() : "";
  if (!rawBasePath || rawBasePath.length > 32_000 || rawBasePath.includes("\0")) {
    throw new TypeError("allowedBaseDir jest wymagany.");
  }
  const configuredBasePath = resolve(rawBasePath);
  const minimum = positiveInteger(minPdfBytes, DEFAULT_MIN_PDF_BYTES, "minPdfBytes");
  const maximum = positiveInteger(maxPdfBytes, DEFAULT_MAX_PDF_BYTES, "maxPdfBytes");
  if (maximum < minimum) throw new TypeError("maxPdfBytes nie moze byc mniejsze niz minPdfBytes.");
  for (const method of ["lstat", "mkdir", "open", "realpath", "rename", "rm"]) {
    if (typeof fileOperations?.[method] !== "function") {
      throw new TypeError(`Brak operacji plikowej: ${method}.`);
    }
  }

  return async function writeDocument({ entry, bytes, signal }) {
    const validBytes = validatePdfBytes(bytes, {
      minPdfBytes: minimum,
      maxPdfBytes: maximum,
    });
    if (!validBytes) {
      const error = new Error("Nieprawidlowy dokument PDF.");
      error.code = "invalid_pdf";
      throw error;
    }
    const rawPath = typeof entry?.outputPath === "string" ? entry.outputPath.trim() : "";
    if (!rawPath || rawPath.length > 32_000 || rawPath.includes("\0")) {
      const error = new Error("Nieprawidlowa sciezka dokumentu.");
      error.code = "write_failed";
      throw error;
    }
    const targetPath = isAbsolute(rawPath)
      ? resolve(rawPath)
      : resolve(configuredBasePath, rawPath);
    if (!isPathInside(configuredBasePath, targetPath)) throw safeWriteError();
    const targetDirectory = dirname(targetPath);
    let temporaryPath = null;
    let handle;
    try {
      const { canonicalBasePath, canonicalTargetDirectory } = await ensureSafeDirectoryTree({
        configuredBasePath,
        targetDirectory,
        fileOperations,
        createMissing: true,
      });
      const safeTargetPath = join(canonicalTargetDirectory, basename(targetPath));
      try {
        const targetInfo = await fileOperations.lstat(safeTargetPath);
        if (targetInfo.isSymbolicLink() || !targetInfo.isFile()) {
          throw safeWriteError();
        }
        const canonicalTargetPath = await fileOperations.realpath(safeTargetPath);
        if (!isPathInside(canonicalBasePath, canonicalTargetPath)) throw safeWriteError();
      } catch (error) {
        if (error?.code !== "ENOENT") throw error;
      }

      await ensureSafeDirectoryTree({
        configuredBasePath,
        targetDirectory,
        fileOperations,
        createMissing: false,
      });
      throwIfAborted(signal);
      temporaryPath = join(
        canonicalTargetDirectory,
        `.portfolio-pdf-download-${randomUUID()}.tmp`,
      );
      handle = await fileOperations.open(temporaryPath, "wx", 0o600);
      await handle.writeFile(validBytes);
      await handle.sync();
      await handle.close();
      handle = null;
      await ensureSafeDirectoryTree({
        configuredBasePath,
        targetDirectory,
        fileOperations,
        createMissing: false,
      });
      throwIfAborted(signal);
      await fileOperations.rename(temporaryPath, safeTargetPath);
      await syncDirectory(canonicalTargetDirectory, fileOperations);
      return { bytesWritten: validBytes.byteLength };
    } catch (error) {
      await handle?.close().catch(() => {});
      if (temporaryPath) {
        await fileOperations.rm(temporaryPath, { force: true }).catch(() => {});
      }
      if (error?.name === "AbortError" || error?.code === "ABORT_ERR") {
        throw abortError();
      }
      if (error?.code === "invalid_pdf") throw error;
      throw safeWriteError();
    }
  };
}

function normalizeFetchResult(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return { type: "global_failure", code: "unknown" };
  }
  if (value.type === "success") {
    return { type: "success", bytes: value.bytes };
  }
  if (value.type === "document_failure") {
    const code = normalizeDocumentFailureCode(value.code);
    return code
      ? { type: "document_failure", code }
      : { type: "global_failure", code: "unknown" };
  }
  if (value.type === "unsupported") {
    const code = normalizeUnsupportedCode(value.code);
    return code
      ? { type: "unsupported", code }
      : { type: "global_failure", code: "unknown" };
  }
  if (value.type === "global_failure") {
    return { type: "global_failure", code: normalizeGlobalCode(value.code) };
  }
  return { type: "global_failure", code: "unknown" };
}

function failureFromThrown(error) {
  return {
    type: "global_failure",
    code: normalizeGlobalCode(error?.code),
  };
}

function publicOutcome(type, index, code, extra = {}) {
  const outcome = { type, index, ...extra };
  if (code) outcome.code = code;
  return outcome;
}

export function createPortfolioPdfDownloadCoordinator({
  retryStore,
  fetchDocument,
  writeDocument,
  allowedBaseDir,
  minPdfBytes = DEFAULT_MIN_PDF_BYTES,
  maxPdfBytes = DEFAULT_MAX_PDF_BYTES,
  maxBatchSize = DEFAULT_MAX_BATCH_SIZE,
  concurrency = DEFAULT_CONCURRENCY,
} = {}) {
  if (
    !retryStore
    || typeof retryStore.getDecisions !== "function"
    || typeof retryStore.recordFailures !== "function"
    || typeof retryStore.recordSuccesses !== "function"
    || typeof retryStore.markUnsupported !== "function"
  ) {
    throw new TypeError("retryStore nie spelnia kontraktu Z10.");
  }
  if (typeof fetchDocument !== "function") throw new TypeError("fetchDocument jest wymagany.");
  const minimum = positiveInteger(minPdfBytes, DEFAULT_MIN_PDF_BYTES, "minPdfBytes");
  const maximum = positiveInteger(maxPdfBytes, DEFAULT_MAX_PDF_BYTES, "maxPdfBytes");
  if (maximum < minimum) throw new TypeError("maxPdfBytes nie moze byc mniejsze niz minPdfBytes.");
  const maximumBatch = positiveInteger(maxBatchSize, DEFAULT_MAX_BATCH_SIZE, "maxBatchSize");
  const workerCount = positiveInteger(concurrency, DEFAULT_CONCURRENCY, "concurrency");
  const documentWriter = writeDocument === undefined
    ? createAtomicPortfolioPdfDocumentWriter({
      allowedBaseDir,
      minPdfBytes: minimum,
      maxPdfBytes: maximum,
    })
    : writeDocument;
  if (typeof documentWriter !== "function") throw new TypeError("writeDocument musi byc funkcja.");

  async function preparePlan(entries, { limit } = {}) {
    if (!Array.isArray(entries) || entries.length === 0 || entries.length > maximumBatch) {
      throw new TypeError("Nieprawidlowa partia dokumentow PDF.");
    }
    const effectiveLimit = positiveInteger(limit, entries.length, "limit");
    const uniqueEntries = [];
    const identities = [];
    const seen = new Set();
    for (let index = 0; index < entries.length; index += 1) {
      const identity = createIdentity(entries[index]);
      if (seen.has(identity.key)) continue;
      seen.add(identity.key);
      uniqueEntries.push({ entry: entries[index], identity, index });
      identities.push(identity);
    }

    const decisionBatch = await retryStore.getDecisions(identities);
    if (
      !decisionBatch
      || !Array.isArray(decisionBatch.decisions)
      || decisionBatch.decisions.length !== uniqueEntries.length
    ) {
      const error = new Error("Nieprawidlowy wynik retry-store.");
      error.code = "store_failed";
      throw error;
    }

    const fresh = [];
    const known = [];
    const dispositions = [];
    const storedUnsupported = [];
    for (let offset = 0; offset < uniqueEntries.length; offset += 1) {
      const item = uniqueEntries[offset];
      const decision = decisionBatch.decisions[offset];
      if (decision?.reason === "new" && decision.shouldAttempt === true) {
        fresh.push(item);
        dispositions.push({ index: item.index, disposition: "fresh" });
      } else if (decision?.reason === "ready" && decision.shouldAttempt === true) {
        known.push(item);
        dispositions.push({ index: item.index, disposition: "retry" });
      } else if (decision?.reason === "backoff" && decision.shouldAttempt === false) {
        dispositions.push({ index: item.index, disposition: "backoff" });
      } else if (decision?.reason === "quarantined" && decision.shouldAttempt === false) {
        dispositions.push({ index: item.index, disposition: "quarantined" });
      } else if (decision?.reason === "unsupported" && decision.shouldAttempt === false) {
        const unsupportedCode = normalizeUnsupportedCode(decision.lastErrorCode);
        if (!unsupportedCode) {
          const error = new Error("Nieprawidlowy kod retry-store.");
          error.code = "store_failed";
          throw error;
        }
        dispositions.push({ index: item.index, disposition: "unsupported" });
        storedUnsupported.push({
          item,
          code: unsupportedCode,
        });
      } else {
        const error = new Error("Nieprawidlowa decyzja retry-store.");
        error.code = "store_failed";
        throw error;
      }
    }

    const orderedCandidates = [...fresh, ...known];
    const scheduled = orderedCandidates.slice(0, effectiveLimit);
    const deferred = orderedCandidates.slice(effectiveLimit);
    const deferredIndexes = new Set(deferred.map((item) => item.index));
    const publicDispositions = dispositions.map((item) => (
      deferredIndexes.has(item.index)
        ? { index: item.index, disposition: "deferred_limit" }
        : item
    )).sort((a, b) => a.index - b.index);
    const counts = publicDispositions.reduce((result, item) => {
      result[item.disposition] = (result[item.disposition] || 0) + 1;
      return result;
    }, {});

    return {
      scheduled,
      storedUnsupported,
      publicPlan: {
        input: entries.length,
        unique: uniqueEntries.length,
        scheduled: scheduled.length,
        fresh: counts.fresh || 0,
        retry: counts.retry || 0,
        backoff: counts.backoff || 0,
        quarantined: counts.quarantined || 0,
        unsupported: counts.unsupported || 0,
        deferredByLimit: counts.deferred_limit || 0,
        dispositions: publicDispositions,
      },
    };
  }

  async function plan(entries, options = {}) {
    return (await preparePlan(entries, options)).publicPlan;
  }

  async function run(entries, options = {}) {
    let prepared;
    try {
      prepared = await preparePlan(entries, options);
    } catch {
      return {
        outcomes: [publicOutcome("global_failure", null, "store_failed", { circuitOpen: true })],
        planning: null,
        circuit: { open: true, code: "store_failed" },
        batchComplete: false,
      };
    }

    const outcomes = prepared.storedUnsupported.map(({ item, code }) => (
      publicOutcome("unsupported", item.index, code, { source: "store" })
    ));
    const successes = [];
    const documentFailures = [];
    const unsupported = [];
    const controller = new AbortController();
    const circuit = {
      open: false,
      code: null,
      consecutiveDelayedFailures: 0,
    };
    let cursor = 0;

    function tripCircuit(code) {
      if (circuit.open) return;
      circuit.open = true;
      circuit.code = code;
      controller.abort();
    }

    function observeGlobalFailure(code) {
      if (IMMEDIATE_GLOBAL_CODES.has(code)) {
        tripCircuit(code);
        return;
      }
      circuit.consecutiveDelayedFailures += 1;
      if (circuit.consecutiveDelayedFailures >= 2) tripCircuit(code);
    }

    function observeDocumentResult() {
      circuit.consecutiveDelayedFailures = 0;
    }

    async function worker() {
      while (!circuit.open) {
        const offset = cursor;
        cursor += 1;
        const item = prepared.scheduled[offset];
        if (!item) return;
        let fetched;
        try {
          fetched = normalizeFetchResult(await fetchDocument({
            entry: item.entry,
            signal: controller.signal,
          }));
        } catch (error) {
          fetched = failureFromThrown(error);
        }

        
        
        if (circuit.open) continue;

        if (fetched.type === "global_failure") {
          outcomes.push(publicOutcome("global_failure", item.index, fetched.code, {
            circuitOpen: IMMEDIATE_GLOBAL_CODES.has(fetched.code)
              || circuit.consecutiveDelayedFailures + 1 >= 2,
          }));
          observeGlobalFailure(fetched.code);
          continue;
        }

        observeDocumentResult();
        if (fetched.type === "document_failure") {
          documentFailures.push({ item, code: fetched.code });
          outcomes.push(publicOutcome("document_failure", item.index, fetched.code));
          continue;
        }
        if (fetched.type === "unsupported") {
          unsupported.push({ item, code: fetched.code });
          outcomes.push(publicOutcome("unsupported", item.index, fetched.code, { source: "fetch" }));
          continue;
        }

        const validBytes = validatePdfBytes(fetched.bytes, {
          minPdfBytes: minimum,
          maxPdfBytes: maximum,
        });
        if (!validBytes) {
          documentFailures.push({ item, code: "invalid_pdf" });
          outcomes.push(publicOutcome("document_failure", item.index, "invalid_pdf"));
          continue;
        }
        if (circuit.open) continue;
        try {
          const writeResult = await documentWriter({
            entry: item.entry,
            bytes: validBytes,
            signal: controller.signal,
          });
          if (circuit.open) continue;
          const outcome = publicOutcome("success", item.index, null, {
            bytesWritten: Number.isSafeInteger(writeResult?.bytesWritten)
              ? writeResult.bytesWritten
              : validBytes.byteLength,
            committed: false,
          });
          successes.push({ item, outcome });
          outcomes.push(outcome);
        } catch (error) {
          if (circuit.open) continue;
          const code = error?.code === "invalid_pdf" ? "invalid_pdf" : "write_failed";
          if (code === "invalid_pdf") {
            documentFailures.push({ item, code });
            outcomes.push(publicOutcome("document_failure", item.index, code));
          } else {
            outcomes.push(publicOutcome("global_failure", item.index, code, {
              circuitOpen: true,
            }));
            tripCircuit(code);
          }
        }
      }
    }

    await Promise.all(Array.from(
      { length: Math.min(workerCount, prepared.scheduled.length) },
      () => worker(),
    ));

    
    
    
    if (!circuit.open) {
      try {
        if (documentFailures.length > 0) {
          await retryStore.recordFailures(documentFailures.map(({ item, code }) => ({
            ...item.identity,
            errorCode: code,
          })));
        }
        for (const { item, code } of unsupported) {
          await retryStore.markUnsupported({
            ...item.identity,
            errorCode: code,
          });
        }
        if (successes.length > 0) {
          await retryStore.recordSuccesses(successes.map(({ item }) => item.identity));
          for (const { outcome } of successes) outcome.committed = true;
        }
      } catch {
        tripCircuit("store_failed");
        outcomes.push(publicOutcome("global_failure", null, "store_failed", {
          circuitOpen: true,
        }));
      }
    }

    outcomes.sort((a, b) => {
      if (a.index === null) return 1;
      if (b.index === null) return -1;
      return a.index - b.index;
    });
    return {
      outcomes,
      planning: prepared.publicPlan,
      circuit: { open: circuit.open, code: circuit.code },
      batchComplete: !circuit.open
        && outcomes.every((outcome) => outcome.type !== "global_failure"),
    };
  }

  return Object.freeze({ plan, run });
}

export const PORTFOLIO_PDF_DOCUMENT_FAILURE_CODES = Object.freeze(
  [...DOCUMENT_FAILURE_CODES],
);
export const PORTFOLIO_PDF_GLOBAL_FAILURE_CODES = Object.freeze(
  [...ALL_GLOBAL_CODES],
);
