import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { resolve, join } from 'node:path';
import { spawnSync } from 'node:child_process';
import { evaluatePayloadGuard } from './updater-lifecycle-3.4.mjs';

const sha256 = value => createHash('sha256').update(value).digest('hex');

function git(repo, args, { allowFailure = false, encoding = 'utf8' } = {}) {
  const result = spawnSync('git', ['-C', repo, ...args], { encoding, maxBuffer: 64 * 1024 * 1024 });
  if (!allowFailure && result.status !== 0) {
    throw new Error(`git ${args.join(' ')} fehlgeschlagen: ${(result.stderr || result.stdout || '').trim()}`);
  }
  return result;
}

function readGitFile(repo, commit, path) {
  const result = git(repo, ['show', `${commit}:${path}`], { allowFailure: true, encoding: null });
  if (result.status !== 0) return null;
  return Buffer.from(result.stdout);
}

function normalize(path) {
  return String(path || '').replaceAll('\\', '/').replace(/^\.\//, '');
}

export function inspectPayload({ packageRoot, repositoryRoot, baseCommit, currentMainCommit }) {
  const source = resolve(packageRoot);
  const repo = resolve(repositoryRoot);
  const manifest = JSON.parse(readFileSync(join(source, 'release-manifest.json'), 'utf8'));
  const baseExists = git(repo, ['cat-file', '-e', `${baseCommit}^{commit}`], { allowFailure: true }).status === 0;
  const mainExists = git(repo, ['cat-file', '-e', `${currentMainCommit}^{commit}`], { allowFailure: true }).status === 0;
  if (!baseExists || !mainExists) {
    return {
      ...evaluatePayloadGuard({ baseCommit: baseExists ? baseCommit : '', currentMainCommit: mainExists ? currentMainCommit : '', baseIsAncestor: false }),
      payloadChangedPaths: [],
      mainChangedPaths: [],
    };
  }
  const baseIsAncestor = git(repo, ['merge-base', '--is-ancestor', baseCommit, currentMainCommit], { allowFailure: true }).status === 0;
  const mainChangedPaths = new Set(
    String(git(repo, ['diff', '--name-only', `${baseCommit}..${currentMainCommit}`, '--']).stdout || '')
      .split(/\r?\n/).map(normalize).filter(Boolean),
  );
  const payloadChangedPaths = new Set();
  const desired = new Map();
  for (const entry of manifest.files || []) {
    const path = normalize(entry.path);
    const baseBytes = readGitFile(repo, baseCommit, path);
    const baseHash = baseBytes === null ? null : sha256(baseBytes);
    if (baseHash !== String(entry.sha256)) payloadChangedPaths.add(path);
    desired.set(path, String(entry.sha256));
  }
  for (const value of manifest.removedFiles || []) {
    const path = normalize(value);
    if (readGitFile(repo, baseCommit, path) !== null) payloadChangedPaths.add(path);
    desired.set(path, null);
  }
  const conflictingPaths = [];
  for (const path of payloadChangedPaths) {
    if (!mainChangedPaths.has(path)) continue;
    const currentBytes = readGitFile(repo, currentMainCommit, path);
    const currentHash = currentBytes === null ? null : sha256(currentBytes);
    if (currentHash !== desired.get(path)) conflictingPaths.push(path);
  }
  return {
    ...evaluatePayloadGuard({ baseCommit, currentMainCommit, baseIsAncestor, conflictingPaths }),
    payloadChangedPaths: [...payloadChangedPaths].sort(),
    mainChangedPaths: [...mainChangedPaths].sort(),
  };
}

if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1].replaceAll('\\', '/')}`).href) {
  const [packageRoot, repositoryRoot, baseCommit, currentMainCommit] = process.argv.slice(2);
  try {
    if (!packageRoot || !repositoryRoot || !baseCommit || !currentMainCommit) {
      throw new Error('Aufruf: node check-release-payload-3.4.mjs <packageRoot> <repositoryRoot> <baseCommit> <currentMainCommit>');
    }
    const result = inspectPayload({ packageRoot, repositoryRoot, baseCommit, currentMainCommit });
    process.stdout.write(`${JSON.stringify(result)}\n`);
    if (!result.allowed) process.exitCode = 2;
  } catch (error) {
    process.stderr.write(`[FEHLER] ${error.message}\n`);
    process.exit(1);
  }
}
