import crypto from 'node:crypto';
import fs from 'node:fs';
import { config } from '../config.js';
import { db, id, now, visibleClause } from '../db.js';
import { camelAsset, camelAssetFolder, camelAssetLink } from './mappers.js';
import { extnameAny, isPathInside, normalizePackagePath, path, sanitizeFileName } from '../utils/pathUtils.js';

function inferKind(mimeType = '', fileName = '') {
  const mime = mimeType.toLowerCase();
  const ext = extnameAny(fileName);
  if (mime.startsWith('image/')) return 'image';
  if (mime.includes('zip') || ['.zip', '.intunewin', '.7z', '.rar'].includes(ext)) return 'archive';
  if (['.msi', '.msp', '.exe'].includes(ext)) return 'installer';
  if (['.ps1', '.psm1', '.psd1', '.cmd', '.bat'].includes(ext)) return 'script';
  if (['.json', '.xml', '.yaml', '.yml', '.config', '.ini', '.reg', '.admx', '.adml'].includes(ext)) return 'config';
  if (mime.startsWith('text/') || ['.txt', '.md', '.log'].includes(ext)) return 'text';
  if (mime === 'application/pdf' || ['.pdf', '.doc', '.docx'].includes(ext)) return 'document';
  return 'generic';
}

function lockerBucket(kind) {
  const map = {
    archive: 'packages',
    config: 'configs',
    document: 'documents',
    generic: 'generic',
    image: 'images',
    installer: 'packages',
    script: 'scripts',
    text: 'documents'
  };
  return map[kind] || 'generic';
}

function storagePathFor(assetId, kind, originalName) {
  return path.join(config.fileLockerDir, 'assets', lockerBucket(kind), assetId, sanitizeFileName(originalName));
}

function checksumFile(filePath) {
  return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
}

function selectAssetById(assetId, userId) {
  return db.prepare(`
    SELECT a.*, f.name AS folder_name, g.name AS group_name
    FROM assets a
    LEFT JOIN asset_folders f ON f.id = a.folder_id
    LEFT JOIN groups g ON g.id = a.group_id
    WHERE a.id = ? AND ${visibleClause('a')}
  `).get(assetId, userId, userId);
}

export function listAssetFolders(userId) {
  return db.prepare(`
    SELECT f.*, g.name AS group_name
    FROM asset_folders f
    LEFT JOIN groups g ON g.id = f.group_id
    WHERE ${visibleClause('f')}
    ORDER BY f.name
  `).all(userId, userId).map(camelAssetFolder);
}

export function createAssetFolder(payload, userId) {
  const folderId = id('afld');
  db.prepare(`
    INSERT INTO asset_folders (id, parent_id, name, description, visibility, owner_user_id, group_id, created_by, created_at, updated_at)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `).run(
    folderId,
    payload.parentId || null,
    payload.name,
    payload.description || '',
    payload.visibility,
    userId,
    payload.visibility === 'group' ? payload.groupId || null : null,
    userId,
    now(),
    now()
  );
  return camelAssetFolder(db.prepare('SELECT * FROM asset_folders WHERE id = ?').get(folderId));
}

export function updateAssetFolder(folderId, payload, userId) {
  const existing = db.prepare(`SELECT * FROM asset_folders WHERE id = ? AND ${visibleClause()}`).get(folderId, userId, userId);
  if (!existing) return null;
  db.prepare(`
    UPDATE asset_folders
    SET parent_id = ?, name = ?, description = ?, visibility = ?, group_id = ?, updated_at = ?
    WHERE id = ?
  `).run(
    payload.parentId ?? existing.parent_id,
    payload.name || existing.name,
    payload.description ?? existing.description ?? '',
    payload.visibility || existing.visibility,
    (payload.visibility || existing.visibility) === 'group' ? payload.groupId ?? existing.group_id : null,
    now(),
    folderId
  );
  return camelAssetFolder(db.prepare('SELECT * FROM asset_folders WHERE id = ?').get(folderId));
}

export function deleteAssetFolder(folderId, userId) {
  db.prepare(`DELETE FROM asset_folders WHERE id = ? AND ${visibleClause()}`).run(folderId, userId, userId);
}

export function listAssets(userId) {
  return db.prepare(`
    SELECT a.*, f.name AS folder_name, g.name AS group_name
    FROM assets a
    LEFT JOIN asset_folders f ON f.id = a.folder_id
    LEFT JOIN groups g ON g.id = a.group_id
    WHERE ${visibleClause('a')}
    ORDER BY a.updated_at DESC, a.name
  `).all(userId, userId).map(camelAsset);
}

export function listExecutableAssets(userId) {
  return db.prepare(`
    SELECT a.*, f.name AS folder_name, g.name AS group_name
    FROM assets a
    LEFT JOIN asset_folders f ON f.id = a.folder_id
    LEFT JOIN groups g ON g.id = a.group_id
    WHERE ${visibleClause('a')}
    ORDER BY a.name
  `).all(userId, userId).map((row) => ({
    id: row.id,
    name: row.name,
    originalName: row.original_name,
    kind: row.kind,
    mimeType: row.mime_type,
    sizeBytes: row.size_bytes,
    checksum: row.checksum,
    packagePath: row.package_path || row.original_name,
    localPath: row.storage_path,
    token: `{{asset:${row.id}}}`
  }));
}

export function getAsset(assetId, userId) {
  const row = selectAssetById(assetId, userId);
  return row ? camelAsset(row) : null;
}

export function getAssetFile(assetId, userId) {
  const row = selectAssetById(assetId, userId);
  if (!row) return null;
  const resolved = path.resolve(row.storage_path);
  if (!isPathInside(config.fileLockerDir, resolved)) throw new Error('Asset file path is outside the configured FileLocker directory');
  return { asset: camelAsset(row), filePath: resolved };
}

export function createAssetFromUpload(file, payload, userId) {
  if (!file?.path) throw new Error('A multipart file field named "file" is required');
  const assetId = id('ast');
  const originalName = sanitizeFileName(payload.originalName || file.originalname);
  const mimeType = payload.mimeType || file.mimetype || 'application/octet-stream';
  const kind = inferKind(mimeType, originalName);
  const storagePath = storagePathFor(assetId, kind, originalName);
  const checksum = checksumFile(file.path);
  fs.mkdirSync(path.dirname(storagePath), { recursive: true });
  fs.renameSync(file.path, storagePath);

  db.prepare(`
    INSERT INTO assets (
      id, folder_id, name, original_name, description, kind, mime_type, size_bytes, checksum, storage_path, package_path,
      visibility, owner_user_id, group_id, created_by, created_at, updated_at
    )
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `).run(
    assetId,
    payload.folderId || null,
    payload.name || originalName,
    originalName,
    payload.description || '',
    kind,
    mimeType,
    file.size,
    checksum,
    storagePath,
    normalizePackagePath(payload.packagePath, originalName),
    payload.visibility,
    userId,
    payload.visibility === 'group' ? payload.groupId || null : null,
    userId,
    now(),
    now()
  );
  return getAsset(assetId, userId);
}

export function updateAsset(assetId, payload, userId) {
  const existing = db.prepare(`SELECT * FROM assets WHERE id = ? AND ${visibleClause()}`).get(assetId, userId, userId);
  if (!existing) return null;
  const visibility = payload.visibility || existing.visibility;
  db.prepare(`
    UPDATE assets
    SET folder_id = ?, name = ?, description = ?, package_path = ?, visibility = ?, group_id = ?, updated_at = ?
    WHERE id = ?
  `).run(
    payload.folderId ?? existing.folder_id,
    payload.name || existing.name,
    payload.description ?? existing.description ?? '',
    normalizePackagePath(payload.packagePath ?? existing.package_path, existing.original_name),
    visibility,
    visibility === 'group' ? payload.groupId ?? existing.group_id : null,
    now(),
    assetId
  );
  return getAsset(assetId, userId);
}

export function deleteAsset(assetId, userId) {
  const row = db.prepare(`SELECT * FROM assets WHERE id = ? AND ${visibleClause()}`).get(assetId, userId, userId);
  if (!row) return false;
  db.prepare('DELETE FROM assets WHERE id = ?').run(assetId);
  try {
    fs.rmSync(row.storage_path, { force: true });
  } catch {
    // Metadata deletion should not fail because an external cleanup already removed the blob.
  }
  return true;
}

export function listAssetLinks(assetId, userId) {
  const asset = selectAssetById(assetId, userId);
  if (!asset) return null;
  return db.prepare(`
    SELECT l.*, a.name AS asset_name
    FROM asset_links l
    LEFT JOIN assets a ON a.id = l.asset_id
    WHERE l.asset_id = ?
    ORDER BY l.created_at DESC
  `).all(assetId).map(camelAssetLink);
}

export function createAssetLink(assetId, payload, userId) {
  const asset = selectAssetById(assetId, userId);
  if (!asset) return null;
  const linkId = id('alnk');
  db.prepare(`
    INSERT INTO asset_links (id, asset_id, target_type, target_id, link_role, package_path, created_by, created_at)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  `).run(
    linkId,
    assetId,
    payload.targetType,
    payload.targetId,
    payload.linkRole,
    normalizePackagePath(payload.packagePath || asset.package_path || asset.original_name),
    userId,
    now()
  );
  return camelAssetLink(db.prepare(`
    SELECT l.*, a.name AS asset_name
    FROM asset_links l
    LEFT JOIN assets a ON a.id = l.asset_id
    WHERE l.id = ?
  `).get(linkId));
}

export function deleteAssetLink(linkId, userId) {
  const link = db.prepare(`
    SELECT l.id
    FROM asset_links l
    INNER JOIN assets a ON a.id = l.asset_id
    WHERE l.id = ? AND ${visibleClause('a')}
  `).get(linkId, userId, userId);
  if (!link) return false;
  db.prepare('DELETE FROM asset_links WHERE id = ?').run(linkId);
  return true;
}
