import { createHash, createPublicKey, verify as verifySignature } from 'crypto';
import { posix as pathPosix } from 'path';
import { PlatformError } from '../../kernel/errors.mjs';
import { validateModuleManifest } from '../../kernel/module-contract.mjs';

export const MODULE_PACKAGE_FORMAT = 'vendoo.module.package';
export const MODULE_PACKAGE_SCHEMA = 1;
export const MAX_PACKAGE_BYTES = 10 * 1024 * 1024;
export const MAX_FILE_COUNT = 200;
export const MAX_FILE_BYTES = 2 * 1024 * 1024;

function stable(value) {
  if (Array.isArray(value)) return value.map(stable);
  if (value && typeof value === 'object') {
    return Object.keys(value).sort().reduce((result, key) => {
      result[key] = stable(value[key]);
      return result;
    }, {});
  }
  return value;
}

export function canonicalJson(value) {
  return JSON.stringify(stable(value));
}

function sha256(value) {
  return createHash('sha256').update(value).digest('hex');
}

export function packageIntegrityPayload(modulePackage) {
  return {
    format: MODULE_PACKAGE_FORMAT,
    schemaVersion: MODULE_PACKAGE_SCHEMA,
    manifest: modulePackage.manifest,
    runtime: modulePackage.runtime,
    files: modulePackage.files.map(file => ({ path: file.path, sha256: file.sha256, size: file.size })),
  };
}

export function calculatePackageDigest(modulePackage) {
  return sha256(canonicalJson(packageIntegrityPayload(modulePackage)));
}

function safeRelativePath(value) {
  const raw = String(value || '').replace(/\\/g, '/').trim();
  const normalized = pathPosix.normalize(raw);
  if (!raw || normalized === '.' || normalized.startsWith('../') || normalized.includes('/../') || normalized.startsWith('/') || /^[A-Za-z]:/.test(raw) || raw.includes('\0')) {
    throw new PlatformError(`Unsicherer Moduldateipfad: ${raw || '(leer)'}`, {
      code: 'MODULE_PACKAGE_PATH_INVALID', status: 400, expose: true,
    });
  }
  return normalized;
}

function validateRuntime(runtime = {}) {
  const mode = String(runtime.mode || 'declarative').trim();
  if (!['builtin-reference', 'declarative', 'isolated'].includes(mode)) {
    throw new PlatformError(`Nicht unterstützter Modul-Runtime-Modus: ${mode}`, { code: 'MODULE_RUNTIME_INVALID', status: 400, expose: true });
  }
  const entry = runtime.entry === undefined || runtime.entry === null || runtime.entry === '' ? null : safeRelativePath(runtime.entry);
  if (mode === 'declarative' && entry) {
    throw new PlatformError('Deklarative Module dürfen keinen ausführbaren Einstiegspunkt besitzen.', { code: 'MODULE_RUNTIME_EXECUTABLE_FORBIDDEN', status: 400, expose: true });
  }
  if (mode === 'isolated' && !entry) {
    throw new PlatformError('Isolierte Module benötigen einen Einstiegspunkt.', { code: 'MODULE_RUNTIME_ENTRY_REQUIRED', status: 400, expose: true });
  }
  return Object.freeze({ mode, entry });
}

function validateFiles(files, runtime) {
  if (!Array.isArray(files)) throw new PlatformError('Moduldateien fehlen.', { code: 'MODULE_PACKAGE_FILES_INVALID', status: 400, expose: true });
  if (files.length > MAX_FILE_COUNT) throw new PlatformError('Das Modulpaket enthält zu viele Dateien.', { code: 'MODULE_PACKAGE_TOO_MANY_FILES', status: 400, expose: true });
  const seen = new Set();
  let total = 0;
  const normalized = files.map(item => {
    if (!item || typeof item !== 'object' || Array.isArray(item)) throw new PlatformError('Ungültiger Moduldateieintrag.', { code: 'MODULE_PACKAGE_FILE_INVALID', status: 400, expose: true });
    const path = safeRelativePath(item.path);
    if (seen.has(path)) throw new PlatformError(`Doppelte Moduldatei: ${path}`, { code: 'MODULE_PACKAGE_FILE_DUPLICATE', status: 400, expose: true });
    seen.add(path);
    const content = String(item.content || '');
    let buffer;
    try { buffer = Buffer.from(content, 'base64'); } catch { buffer = null; }
    if (!buffer || buffer.length > MAX_FILE_BYTES) throw new PlatformError(`Moduldatei ist ungültig oder zu groß: ${path}`, { code: 'MODULE_PACKAGE_FILE_TOO_LARGE', status: 400, expose: true });
    const digest = sha256(buffer);
    if (!/^[a-f0-9]{64}$/.test(String(item.sha256 || '')) || digest !== String(item.sha256)) {
      throw new PlatformError(`Prüfsumme stimmt nicht: ${path}`, { code: 'MODULE_PACKAGE_FILE_HASH_MISMATCH', status: 400, expose: true });
    }
    if (Number(item.size) !== buffer.length) throw new PlatformError(`Dateigröße stimmt nicht: ${path}`, { code: 'MODULE_PACKAGE_FILE_SIZE_MISMATCH', status: 400, expose: true });
    total += buffer.length;
    if (runtime.mode === 'declarative' && /\.(?:mjs|cjs|js|node|exe|dll|so|dylib|ps1|bat|cmd|sh)$/i.test(path)) {
      throw new PlatformError(`Deklaratives Modul enthält ausführbaren Code: ${path}`, { code: 'MODULE_PACKAGE_EXECUTABLE_FORBIDDEN', status: 400, expose: true });
    }
    return Object.freeze({ path, sha256: digest, size: buffer.length, content });
  });
  if (total > MAX_PACKAGE_BYTES) throw new PlatformError('Das Modulpaket ist zu groß.', { code: 'MODULE_PACKAGE_TOO_LARGE', status: 400, expose: true });
  if (runtime.entry && !seen.has(runtime.entry)) throw new PlatformError('Der Runtime-Einstiegspunkt fehlt im Paket.', { code: 'MODULE_RUNTIME_ENTRY_MISSING', status: 400, expose: true });
  return Object.freeze(normalized);
}

function verifyOptionalSignature(modulePackage, trustKeys = {}) {
  const signature = modulePackage.signature;
  if (!signature) return { signed: false, trusted: false, keyId: null };
  const algorithm = String(signature.algorithm || '').toLowerCase();
  const keyId = String(signature.keyId || '').trim();
  const value = String(signature.value || '').trim();
  if (algorithm !== 'ed25519' || !keyId || !value) {
    throw new PlatformError('Ungültige Modulsignatur.', { code: 'MODULE_SIGNATURE_INVALID', status: 400, expose: true });
  }
  const publicKey = trustKeys[keyId];
  if (!publicKey) return { signed: true, trusted: false, keyId };
  try {
    const verified = verifySignature(null, Buffer.from(modulePackage.integrity.digest, 'hex'), createPublicKey(publicKey), Buffer.from(value, 'base64'));
    if (!verified) throw new Error('Signaturprüfung fehlgeschlagen');
    return { signed: true, trusted: true, keyId };
  } catch (error) {
    throw new PlatformError('Modulsignatur konnte nicht bestätigt werden.', { code: 'MODULE_SIGNATURE_REJECTED', status: 400, expose: true, cause: error });
  }
}

export function validateModulePackage(input, { trustKeys = {}, allowBuiltinReference = false } = {}) {
  const source = input && typeof input === 'object' && !Array.isArray(input) ? input : null;
  if (!source) throw new PlatformError('Ungültiges Modulpaket.', { code: 'MODULE_PACKAGE_INVALID', status: 400, expose: true });
  if (source.format !== MODULE_PACKAGE_FORMAT || Number(source.schemaVersion) !== MODULE_PACKAGE_SCHEMA) {
    throw new PlatformError('Nicht unterstütztes Modulpaketformat.', { code: 'MODULE_PACKAGE_VERSION_UNSUPPORTED', status: 400, expose: true });
  }
  const manifest = validateModuleManifest(source.manifest);
  const runtime = validateRuntime(source.runtime);
  if (runtime.mode === 'builtin-reference' && !allowBuiltinReference) {
    throw new PlatformError('Referenzexporte eingebauter Module können nicht installiert werden.', { code: 'MODULE_BUILTIN_REFERENCE_NOT_INSTALLABLE', status: 400, expose: true });
  }
  if (manifest.type !== 'extension-host' && runtime.mode !== 'builtin-reference') {
    throw new PlatformError('Installierbare Fremdmodule müssen den Typ extension-host verwenden.', { code: 'MODULE_EXTERNAL_TYPE_REQUIRED', status: 400, expose: true });
  }
  const files = validateFiles(source.files || [], runtime);
  const normalized = {
    format: MODULE_PACKAGE_FORMAT,
    schemaVersion: MODULE_PACKAGE_SCHEMA,
    manifest,
    runtime,
    files,
    createdAt: String(source.createdAt || new Date().toISOString()),
    integrity: {
      algorithm: String(source.integrity?.algorithm || '').toLowerCase(),
      digest: String(source.integrity?.digest || '').toLowerCase(),
    },
    signature: source.signature || null,
  };
  if (normalized.integrity.algorithm !== 'sha256' || !/^[a-f0-9]{64}$/.test(normalized.integrity.digest)) {
    throw new PlatformError('Paketintegrität fehlt oder ist ungültig.', { code: 'MODULE_PACKAGE_INTEGRITY_INVALID', status: 400, expose: true });
  }
  const expected = calculatePackageDigest(normalized);
  if (expected !== normalized.integrity.digest) {
    throw new PlatformError('Paketintegrität stimmt nicht.', { code: 'MODULE_PACKAGE_INTEGRITY_MISMATCH', status: 400, expose: true });
  }
  const trust = verifyOptionalSignature(normalized, trustKeys);
  return Object.freeze({ ...normalized, trust: Object.freeze(trust) });
}

export function buildModulePackage({ manifest, runtime = { mode: 'builtin-reference', entry: null }, files = [], createdAt = new Date().toISOString() }) {
  const normalizedFiles = files.map(file => {
    const path = safeRelativePath(file.path);
    const buffer = Buffer.isBuffer(file.content) ? file.content : Buffer.from(String(file.content || ''), 'utf8');
    return { path, sha256: sha256(buffer), size: buffer.length, content: buffer.toString('base64') };
  });
  const draft = { format: MODULE_PACKAGE_FORMAT, schemaVersion: MODULE_PACKAGE_SCHEMA, manifest, runtime, files: normalizedFiles, createdAt, integrity: { algorithm: 'sha256', digest: '' }, signature: null };
  draft.integrity.digest = calculatePackageDigest(draft);
  return validateModulePackage(draft, { allowBuiltinReference: true });
}
