const unzipper = require('unzipper');
const fs = require('fs-extra');
const path = require('path');
const { pipeline } = require('stream/promises');
const { getDestinationDecision, getCadInfo } = require('./router');
const { buildDestinationIndex } = require('./destinationIndex');
const { buildExistingCadKeyIndex, toCadKey } = require('./duplicateIndex');
const { prepareUnroutedTarget, prepareDuplicateTarget } = require('./unrouted');

function parseNumericVersion(version) {
  const rawVersion = String(version || '').trim();
  if (!rawVersion || !/^\d+$/.test(rawVersion)) {
    return null;
  }

  try {
    return BigInt(rawVersion);
  } catch {
    return null;
  }
}

async function buildZipHighestNumericVersionByCadKey(zipPath) {
  const index = new Map();
  let directory;

  try {
    directory = await unzipper.Open.file(zipPath);
  } catch {
    return index;
  }

  for (const row of directory.files || []) {
    if (row.type !== 'File') {
      continue;
    }

    const baseName = path.basename(row.path || '');
    const cadInfo = getCadInfo(baseName);
    if (!cadInfo) {
      continue;
    }

    const version = parseNumericVersion(cadInfo.version);
    if (version === null) {
      continue;
    }

    const key = toCadKey(cadInfo);
    const current = index.get(key);
    if (current === undefined || version > current) {
      index.set(key, version);
    }
  }

  return index;
}

async function processZip(zipPath, config) {
  const stream = fs.createReadStream(zipPath).pipe(unzipper.Parse({ forceStream: true }));
  const destinationIndex = await buildDestinationIndex(config?.destination);
  const existingCadKeys = await buildExistingCadKeyIndex(config?.destination);
  const sourceMaxVersions = await buildZipHighestNumericVersionByCadKey(zipPath);
  const result = {
    scanned: 0,
    copied: 0,
    skipped: 0,
    unrouted: 0,
    duplicates: 0,
    details: [],
  };

  for await (const entry of stream) {
    if (entry.type !== 'File') {
      entry.autodrain();
      continue;
    }

    const file = entry.path;
    const baseName = path.basename(file);
    result.scanned += 1;

    const cadInfo = getCadInfo(baseName);
    if (!cadInfo) {
      result.skipped += 1;
      entry.autodrain();
      continue;
    }

    if (existingCadKeys.has(toCadKey(cadInfo))) {
      const incomingVersion = parseNumericVersion(cadInfo.version);
      const highestInSource = sourceMaxVersions.get(toCadKey(cadInfo));
      if (incomingVersion !== null && highestInSource !== undefined && incomingVersion < highestInSource) {
        result.details.push({
          file: baseName,
          reason: 'Versione piu alta presente nei file da smistare',
        });
        entry.autodrain();
        continue;
      }

      const duplicateTarget = await prepareDuplicateTarget(baseName);
      if (!duplicateTarget.shouldCopy) {
        result.details.push({
          file: baseName,
          destination: duplicateTarget.destinationDir,
          reason: duplicateTarget.reason || 'Versione piu alta gia presente',
        });
        entry.autodrain();
        continue;
      }

      await pipeline(entry, fs.createWriteStream(duplicateTarget.destinationPath));

      result.copied += 1;
      result.duplicates += 1;
      result.details.push({
        file: baseName,
        destination: duplicateTarget.destinationDir,
        reason: 'Duplicato gia presente prima dello smistamento',
      });
      continue;
    }

    const decision = getDestinationDecision(baseName, config, destinationIndex);
    const destDir = decision.destination;

    if (!destDir) {
      const unroutedTarget = await prepareUnroutedTarget(baseName);
      if (!unroutedTarget.shouldCopy) {
        result.details.push({
          file: baseName,
          destination: unroutedTarget.destinationDir,
          reason: unroutedTarget.reason || 'Versione piu alta gia presente',
        });
        entry.autodrain();
        continue;
      }

      await pipeline(entry, fs.createWriteStream(unroutedTarget.destinationPath));

      result.copied += 1;
      result.unrouted += 1;
      result.details.push({
        file: baseName,
        destination: unroutedTarget.destinationDir,
        reason: decision.reason || 'Nessuna regola trovata',
      });
      continue;
    }

    const dest = path.join(destDir, baseName);
    await pipeline(entry, fs.createWriteStream(dest));

    result.copied += 1;
    result.details.push({ file: baseName, destination: destDir });
  }

  return result;
}

module.exports = { processZip };
