import { createHash, randomBytes, timingSafeEqual } from 'crypto';
import { db } from './db.mjs';

export const PERMISSION_CATALOG = Object.freeze([
  { key: 'dashboard.view', label: 'Dashboard ansehen', group: 'Allgemein' },
  { key: 'listings.view', label: 'Artikel ansehen', group: 'Artikel' },
  { key: 'listings.edit', label: 'Artikel erstellen und bearbeiten', group: 'Artikel' },
  { key: 'listings.delete', label: 'Artikel in den Papierkorb verschieben', group: 'Artikel' },
  { key: 'listings.permanent_delete', label: 'Artikel endgültig löschen', group: 'Artikel' },
  { key: 'media.view', label: 'Bilder ansehen und herunterladen', group: 'Bilder' },
  { key: 'media.edit', label: 'Bilder hochladen und bearbeiten', group: 'Bilder' },
  { key: 'media.delete', label: 'Bilder löschen', group: 'Bilder' },
  { key: 'generation.execute', label: 'AI- und FLUX-Generierung starten', group: 'AI & FLUX' },
  { key: 'batch.execute', label: 'Bildproduktion ausführen', group: 'AI & FLUX' },
  { key: 'quality.execute', label: 'Qualitätsanalysen und AI-Verbesserungen', group: 'Qualität' },
  { key: 'publishing.view', label: 'Publishing ansehen', group: 'Publishing' },
  { key: 'publishing.execute', label: 'Veröffentlichungen starten und wiederholen', group: 'Publishing' },
  { key: 'publishing.manage', label: 'Veröffentlichungen beenden und endgültig löschen', group: 'Publishing' },
  { key: 'inventory.edit', label: 'Lagerorte und Bestände ändern', group: 'Organisation' },
  { key: 'templates.edit', label: 'Vorlagen verwalten', group: 'Organisation' },
  { key: 'settings.manage', label: 'Globale Einstellungen ändern', group: 'Administration' },
  { key: 'users.manage', label: 'Benutzer und Rollen verwalten', group: 'Administration' },
  { key: 'sessions.manage', label: 'Fremde Sitzungen beenden', group: 'Administration' },
  { key: 'audit.view', label: 'Audit-Protokoll ansehen und exportieren', group: 'Administration' },
  { key: 'operations.manage', label: 'Backups, Restore und lokale Updates verwalten', group: 'Administration' },
  { key: 'updates.manage', label: 'GitHub-/Coolify-Deployments auslösen', group: 'Administration' },
  { key: 'security.manage', label: 'Server-Sicherheit konfigurieren', group: 'Administration' },
  { key: 'modules.manage', label: 'Module installieren, aktivieren und entfernen', group: 'Administration' },
]);

const ALL_PERMISSIONS = PERMISSION_CATALOG.map(item => item.key);

export const ROLE_DEFINITIONS = Object.freeze({
  admin: {
    key: 'admin',
    label: 'Administrator',
    description: 'Vollzugriff auf Vendoo, Benutzer, Backups, Updates und Server-Sicherheit.',
    permissions: ALL_PERMISSIONS,
  },
  manager: {
    key: 'manager',
    label: 'Manager',
    description: 'Operative Leitung mit vollständiger Artikel-, Medien- und Publishing-Verwaltung, ohne System- und Benutzeradministration.',
    permissions: ALL_PERMISSIONS.filter(key => ![
      'users.manage', 'sessions.manage', 'operations.manage', 'updates.manage', 'security.manage', 'settings.manage', 'modules.manage',
    ].includes(key)),
  },
  editor: {
    key: 'editor',
    label: 'Editor',
    description: 'Erstellt und bearbeitet Artikel, Bilder und AI-Inhalte. Keine endgültigen Löschungen oder Systemverwaltung.',
    permissions: [
      'dashboard.view', 'listings.view', 'listings.edit', 'listings.delete',
      'media.view', 'media.edit', 'generation.execute', 'batch.execute', 'quality.execute',
      'publishing.view', 'publishing.execute', 'inventory.edit', 'templates.edit',
    ],
  },
  publisher: {
    key: 'publisher',
    label: 'Publisher',
    description: 'Prüft Artikel und führt Veröffentlichungen aus, ohne Artikelstammdaten oder Systemeinstellungen zu ändern.',
    permissions: [
      'dashboard.view', 'listings.view', 'media.view', 'quality.execute',
      'publishing.view', 'publishing.execute',
    ],
  },
  viewer: {
    key: 'viewer',
    label: 'Leser',
    description: 'Reiner Lesezugriff auf Artikel, Bilder, Qualität und Publishing-Status.',
    permissions: ['dashboard.view', 'listings.view', 'media.view', 'publishing.view'],
  },
});

const MUTATION_DEFAULT_PERMISSION = 'listings.edit';

export function normalizeRole(role) {
  return ROLE_DEFINITIONS[role] ? role : 'viewer';
}

export function getRoleDefinitions() {
  return Object.values(ROLE_DEFINITIONS).map(role => ({ ...role, permissions: [...role.permissions] }));
}

export function getPermissionCatalog() {
  return PERMISSION_CATALOG.map(item => ({ ...item }));
}

export function permissionsForRole(role) {
  return new Set(ROLE_DEFINITIONS[normalizeRole(role)].permissions);
}

export function hasPermission(role, permission) {
  if (!permission) return true;
  return permissionsForRole(role).has(permission);
}

export function requiredPermissionForApi(method, path) {
  const verb = String(method || 'GET').toUpperCase();
  const route = String(path || '').split('?')[0];

  if (route === '/api/me' || route.startsWith('/api/my/')) return null;
  if (route === '/api/extensions/heartbeat' || route === '/api/extensions/prepare') return null;

  if (route === '/api/backup' || route === '/api/restore') return 'operations.manage';
  if (route.startsWith('/api/admin/users')) return 'users.manage';
  if (route.startsWith('/api/admin/sessions')) return 'sessions.manage';
  if (route.startsWith('/api/admin/login-history')) return 'users.manage';
  if (route.startsWith('/api/admin/smtp')) return 'settings.manage';
  if (route.startsWith('/api/admin/modules')) return 'modules.manage';
  if (route.startsWith('/api/admin/deployment')) return 'updates.manage';
  if (route.startsWith('/api/admin/audit')) return 'audit.view';
  if (route.startsWith('/api/admin/') || route.startsWith('/api/operations/')) return 'operations.manage';
  if (route.startsWith('/api/settings')) return verb === 'GET' ? null : 'settings.manage';
  if (route.startsWith('/api/themes')) return null; // sichere Detail-Policies übernimmt der Platform Kernel
  if (route.startsWith('/api/extensions/diagnostics')) return 'security.manage';
  if (route.startsWith('/api/security/')) return verb === 'GET' ? 'security.manage' : 'security.manage';
  if (route.startsWith('/api/locks/admin/')) return 'sessions.manage';

  if (route.startsWith('/api/trash')) {
    if (verb === 'GET') return 'listings.view';
    if (verb === 'DELETE') return 'listings.permanent_delete';
    return 'listings.edit';
  }
  if (route.startsWith('/api/listings')) {
    if (verb === 'GET') return 'listings.view';
    if (verb === 'DELETE') return 'listings.delete';
    return 'listings.edit';
  }
  if (route.startsWith('/api/templates')) return verb === 'GET' ? null : 'templates.edit';
  if (route.startsWith('/api/storage') || route.startsWith('/api/inventory')) return verb === 'GET' ? null : 'inventory.edit';

  if (route.startsWith('/api/media') || route.startsWith('/api/gallery')) {
    if (verb === 'GET') return 'media.view';
    if (verb === 'DELETE') return 'media.delete';
    return 'media.edit';
  }
  if (route.startsWith('/api/upload') || route.startsWith('/api/images') || route.startsWith('/api/image-editor') || route.startsWith('/api/mobile-upload-sessions')) {
    if (verb === 'GET') return 'media.view';
    if (verb === 'DELETE') return 'media.delete';
    return 'media.edit';
  }
  if (route === '/api/html-render' || route === '/api/html-wrap') return 'listings.view';
  if (route === '/api/photos/archive') return 'publishing.execute';
  if (route.startsWith('/api/local-image/install') || route.startsWith('/api/local-image/update') || route.startsWith('/api/local-image/uninstall') || route.startsWith('/api/local-image/regenerate-token')) return 'settings.manage';
  if (route.startsWith('/api/ebay')) {
    if (verb === 'GET') return 'publishing.view';
    if (verb === 'DELETE') return 'publishing.manage';
    return 'publishing.execute';
  }
  if (route.startsWith('/api/flux') || route.startsWith('/api/local-image') || route.startsWith('/api/ai-model') || route === '/api/generate' || route.startsWith('/api/prompt-lab')) {
    return verb === 'GET' ? 'media.view' : 'generation.execute';
  }
  if (route.startsWith('/api/image-batch')) return verb === 'GET' ? 'media.view' : 'batch.execute';
  if (route.startsWith('/api/quality')) return verb === 'GET' ? 'listings.view' : 'quality.execute';
  if (route.startsWith('/api/publishing') || route.startsWith('/api/extension/publishing') || route.startsWith('/api/publish') || route.startsWith('/api/schedule')) {
    if (verb === 'GET') return 'publishing.view';
    if (verb === 'DELETE' || route.includes('/end') || route.includes('/delete')) return 'publishing.manage';
    return 'publishing.execute';
  }
  if (route.startsWith('/api/extension/queue') || route.startsWith('/api/extension/listing')) return 'publishing.view';

  if (['GET', 'HEAD', 'OPTIONS'].includes(verb)) return null;
  return MUTATION_DEFAULT_PERMISSION;
}

// --- Resource locks -------------------------------------------------------

db.exec(`
  CREATE TABLE IF NOT EXISTS resource_locks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    resource_type TEXT NOT NULL,
    resource_id TEXT NOT NULL,
    user_id INTEGER NOT NULL,
    token_hash TEXT NOT NULL UNIQUE,
    acquired_at TEXT NOT NULL DEFAULT (datetime('now')),
    renewed_at TEXT NOT NULL DEFAULT (datetime('now')),
    expires_at TEXT NOT NULL,
    client_label TEXT,
    FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
    UNIQUE(resource_type, resource_id)
  );
  CREATE INDEX IF NOT EXISTS idx_resource_locks_expiry ON resource_locks(expires_at);
  CREATE INDEX IF NOT EXISTS idx_resource_locks_user ON resource_locks(user_id, expires_at);
`);

try {
  db.prepare(`INSERT OR IGNORE INTO system_migrations (migration_key, app_version, description)
    VALUES ('multiuser-security-1.31.0', '1.31.0', 'Rollen, sichere Sitzungen, Bearbeitungssperren und Server-Härtung')`).run();
} catch {}

function hashLockToken(token) {
  return createHash('sha256').update(String(token || '')).digest('hex');
}

function safeEqualHex(a, b) {
  try {
    const aa = Buffer.from(String(a || ''), 'hex');
    const bb = Buffer.from(String(b || ''), 'hex');
    return aa.length > 0 && aa.length === bb.length && timingSafeEqual(aa, bb);
  } catch { return false; }
}

export function cleanExpiredResourceLocks() {
  return db.prepare("DELETE FROM resource_locks WHERE expires_at <= datetime('now')").run().changes;
}

function hydrateLock(row) {
  if (!row) return null;
  return {
    id: row.id,
    resource_type: row.resource_type,
    resource_id: row.resource_id,
    user_id: row.user_id,
    user_name: row.user_name || '',
    user_email: row.user_email || '',
    acquired_at: row.acquired_at,
    renewed_at: row.renewed_at,
    expires_at: row.expires_at,
    client_label: row.client_label || '',
  };
}

export function getResourceLock(resourceType, resourceId) {
  cleanExpiredResourceLocks();
  const row = db.prepare(`SELECT l.*, u.name AS user_name, u.email AS user_email
    FROM resource_locks l JOIN users u ON u.id = l.user_id
    WHERE l.resource_type = ? AND l.resource_id = ? AND l.expires_at > datetime('now')`).get(String(resourceType), String(resourceId));
  return hydrateLock(row);
}

export function listResourceLocks() {
  cleanExpiredResourceLocks();
  return db.prepare(`SELECT l.*, u.name AS user_name, u.email AS user_email
    FROM resource_locks l JOIN users u ON u.id = l.user_id
    WHERE l.expires_at > datetime('now') ORDER BY l.renewed_at DESC`).all().map(hydrateLock);
}

export function acquireResourceLock({ resourceType, resourceId, userId, clientLabel = '', ttlSeconds = 120 }) {
  cleanExpiredResourceLocks();
  const type = String(resourceType || '').trim().slice(0, 40);
  const id = String(resourceId || '').trim().slice(0, 120);
  if (!type || !id) throw new Error('Ressourcentyp und ID sind erforderlich');
  const existing = getResourceLock(type, id);
  if (existing && Number(existing.user_id) !== Number(userId)) {
    const error = new Error(`${existing.user_name || existing.user_email || 'Ein anderer Benutzer'} bearbeitet diesen Datensatz bereits.`);
    error.code = 'LOCKED';
    error.lock = existing;
    throw error;
  }

  const token = randomBytes(32).toString('hex');
  const tokenHash = hashLockToken(token);
  const ttl = Math.max(45, Math.min(600, Number(ttlSeconds) || 120));
  const expiresAt = new Date(Date.now() + ttl * 1000).toISOString();
  const transaction = db.transaction(() => {
    db.prepare('DELETE FROM resource_locks WHERE resource_type = ? AND resource_id = ?').run(type, id);
    db.prepare(`INSERT INTO resource_locks
      (resource_type, resource_id, user_id, token_hash, expires_at, client_label)
      VALUES (?, ?, ?, ?, ?, ?)`).run(type, id, userId, tokenHash, expiresAt, String(clientLabel || '').slice(0, 160));
  });
  transaction();
  return { token, lock: getResourceLock(type, id) };
}

export function validateResourceLock({ resourceType, resourceId, userId, token }) {
  const type = String(resourceType || '');
  const id = String(resourceId || '');
  const row = db.prepare(`SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND expires_at > datetime('now')`).get(type, id);
  if (!row) return { valid: true, lock: null };
  const providedHash = hashLockToken(token);
  const valid = Number(row.user_id) === Number(userId) && safeEqualHex(row.token_hash, providedHash);
  return { valid, lock: getResourceLock(type, id) };
}

export function renewResourceLock({ resourceType, resourceId, userId, token, ttlSeconds = 120 }) {
  const validation = validateResourceLock({ resourceType, resourceId, userId, token });
  if (!validation.valid || !validation.lock) {
    const error = new Error('Bearbeitungssperre ist abgelaufen oder gehört zu einer anderen Sitzung.');
    error.code = 'LOCK_INVALID';
    error.lock = validation.lock;
    throw error;
  }
  const ttl = Math.max(45, Math.min(600, Number(ttlSeconds) || 120));
  const expiresAt = new Date(Date.now() + ttl * 1000).toISOString();
  db.prepare(`UPDATE resource_locks SET renewed_at = datetime('now'), expires_at = ?
    WHERE resource_type = ? AND resource_id = ?`).run(expiresAt, String(resourceType), String(resourceId));
  return getResourceLock(resourceType, resourceId);
}

export function releaseResourceLock({ resourceType, resourceId, userId, token, force = false }) {
  const type = String(resourceType || '');
  const id = String(resourceId || '');
  if (force) return db.prepare('DELETE FROM resource_locks WHERE resource_type = ? AND resource_id = ?').run(type, id).changes;
  const row = db.prepare('SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ?').get(type, id);
  if (!row) return 0;
  const valid = Number(row.user_id) === Number(userId) && safeEqualHex(row.token_hash, hashLockToken(token));
  if (!valid) {
    const error = new Error('Bearbeitungssperre kann nicht durch diese Sitzung aufgehoben werden.');
    error.code = 'LOCK_INVALID';
    throw error;
  }
  return db.prepare('DELETE FROM resource_locks WHERE id = ?').run(row.id).changes;
}

// --- Rate limiter ---------------------------------------------------------
const rateBuckets = new Map();
const rateStats = { allowed: 0, blocked: 0, buckets: 0, last_blocked_at: null };

export function consumeRateLimit(key, { limit = 120, windowMs = 60_000 } = {}) {
  const now = Date.now();
  const normalizedKey = String(key || 'anonymous');
  let bucket = rateBuckets.get(normalizedKey);
  if (!bucket || now >= bucket.resetAt) bucket = { count: 0, resetAt: now + windowMs };
  bucket.count += 1;
  rateBuckets.set(normalizedKey, bucket);
  rateStats.buckets = rateBuckets.size;
  const remaining = Math.max(0, limit - bucket.count);
  const allowed = bucket.count <= limit;
  if (allowed) rateStats.allowed += 1;
  else {
    rateStats.blocked += 1;
    rateStats.last_blocked_at = new Date().toISOString();
  }
  if (rateBuckets.size > 5000) {
    for (const [entryKey, entry] of rateBuckets) if (entry.resetAt <= now) rateBuckets.delete(entryKey);
  }
  return { allowed, remaining, retryAfter: Math.max(1, Math.ceil((bucket.resetAt - now) / 1000)), resetAt: bucket.resetAt };
}

export function getRateLimitStats() {
  return { ...rateStats, buckets: rateBuckets.size };
}

setInterval(cleanExpiredResourceLocks, 60_000).unref?.();
