import fs from 'node:fs';
import path from 'node:path';
import { DatabaseSync } from 'node:sqlite';

const SQLITE_HEADER = Buffer.from('SQLite format 3\u0000', 'binary');
const SIDECAR_SUFFIXES = ['', '-wal', '-shm', '-journal'];

function safeTimestamp(date = new Date()) {
  return date.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
}

export function isDatabaseCorruptionError(error) {
  const message = String(error?.message || error || '').toLowerCase();
  return error?.errcode === 11
    || message.includes('database disk image is malformed')
    || message.includes('database is malformed')
    || message.includes('database corrupt')
    || message.includes('database corruption')
    || message.includes('file is not a database')
    || message.includes('not a database');
}

export function isDatabaseBusyError(error) {
  const message = String(error?.message || error || '').toLowerCase();
  return error?.errcode === 5
    || error?.errcode === 6
    || message.includes('database is locked')
    || message.includes('database table is locked')
    || message.includes('database is busy');
}

function hasSqliteHeader(dbPath) {
  const fd = fs.openSync(dbPath, 'r');
  try {
    const buffer = Buffer.alloc(SQLITE_HEADER.length);
    const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
    return bytesRead === SQLITE_HEADER.length && buffer.equals(SQLITE_HEADER);
  } finally {
    fs.closeSync(fd);
  }
}

/**
 * Performs a read-only SQLite health check without modifying the database.
 * The returned object is intentionally serializable for diagnostics.
 */
export function inspectDatabaseFile(dbPath, { full = false } = {}) {
  const absolutePath = path.resolve(dbPath);

  if (!fs.existsSync(absolutePath)) {
    return { status: 'missing', dbPath: absolutePath, size: 0, details: 'Database file does not exist.' };
  }

  const stat = fs.statSync(absolutePath);
  if (stat.size === 0) {
    return { status: 'empty', dbPath: absolutePath, size: 0, details: 'Database file is empty.' };
  }

  try {
    if (!hasSqliteHeader(absolutePath)) {
      return {
        status: 'corrupt',
        dbPath: absolutePath,
        size: stat.size,
        details: 'SQLite file header is invalid.'
      };
    }
  } catch (error) {
    return {
      status: 'unreadable',
      dbPath: absolutePath,
      size: stat.size,
      details: error?.message || String(error)
    };
  }

  let db;
  try {
    db = new DatabaseSync(absolutePath, { readOnly: true });
    db.exec('PRAGMA busy_timeout = 5000;');
    // Reading schema_version forces SQLite to parse the schema before the check.
    db.prepare('PRAGMA schema_version').get();
    const rows = db.prepare(full ? 'PRAGMA integrity_check' : 'PRAGMA quick_check').all();
    const results = rows.map((row) => String(Object.values(row)[0] ?? '')).filter(Boolean);
    const healthy = results.length > 0 && results.every((value) => value.toLowerCase() === 'ok');
    return {
      status: healthy ? 'healthy' : 'corrupt',
      dbPath: absolutePath,
      size: stat.size,
      check: full ? 'integrity_check' : 'quick_check',
      results,
      details: healthy ? 'ok' : results.join('; ') || 'SQLite integrity check failed.'
    };
  } catch (error) {
    if (isDatabaseBusyError(error)) {
      return {
        status: 'locked',
        dbPath: absolutePath,
        size: stat.size,
        details: error?.message || String(error),
        errorCode: error?.code || '',
        errcode: error?.errcode ?? null
      };
    }
    return {
      status: isDatabaseCorruptionError(error) ? 'corrupt' : 'unreadable',
      dbPath: absolutePath,
      size: stat.size,
      details: error?.message || String(error),
      errorCode: error?.code || '',
      errcode: error?.errcode ?? null
    };
  } finally {
    try { db?.close(); } catch {}
  }
}

function moveFilePreserving(source, destination) {
  try {
    fs.renameSync(source, destination);
  } catch (error) {
    if (error?.code !== 'EXDEV') throw error;
    fs.copyFileSync(source, destination);
    fs.rmSync(source, { force: true });
  }
}

/**
 * Moves the damaged database and all SQLite sidecar files to a timestamped
 * recovery directory. No user data is deleted.
 */
export function quarantineDatabaseFiles(dbPath, {
  reason = 'Database integrity check failed.',
  recoveryRoot,
  now = new Date()
} = {}) {
  const absolutePath = path.resolve(dbPath);
  const dataDir = path.dirname(absolutePath);
  const root = path.resolve(recoveryRoot || path.join(dataDir, 'recovery'));
  const stamp = safeTimestamp(now);
  let targetDir = path.join(root, `database-${stamp}`);
  let counter = 1;
  while (fs.existsSync(targetDir)) {
    targetDir = path.join(root, `database-${stamp}-${counter}`);
    counter += 1;
  }
  fs.mkdirSync(targetDir, { recursive: true });

  const movedFiles = [];
  for (const suffix of SIDECAR_SUFFIXES) {
    const source = `${absolutePath}${suffix}`;
    if (!fs.existsSync(source)) continue;
    const destination = path.join(targetDir, path.basename(source));
    moveFilePreserving(source, destination);
    movedFiles.push(destination);
  }

  const manifest = {
    createdAt: now.toISOString(),
    originalDatabasePath: absolutePath,
    reason,
    movedFiles: movedFiles.map((file) => path.basename(file)),
    instructions: [
      'These files were preserved because the SQLite database failed an integrity check.',
      'Do not copy them back over a healthy database.',
      'For production data recovery, keep this directory and use a SQLite recovery specialist/tool.'
    ]
  };
  fs.writeFileSync(path.join(targetDir, 'recovery-manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
  fs.writeFileSync(path.join(targetDir, 'BENI-OKU.txt'), [
    'ONLYCOIN VERITABANI KURTARMA KOPYASI',
    '=====================================',
    `Tarih: ${manifest.createdAt}`,
    `Eski konum: ${manifest.originalDatabasePath}`,
    `Neden: ${manifest.reason}`,
    '',
    'Bozuk veritabani silinmedi; bu klasore tasindi.',
    'Bu dosyalari yeni calisan veritabaninin ustune kopyalamayin.',
    'Gercek musteri verisi varsa klasoru saklayin ve profesyonel SQLite kurtarma islemi uygulayin.',
    ''
  ].join('\r\n'), 'utf8');

  return { targetDir, movedFiles, manifest };
}

export function checkpointAndVerifyDatabase(dbPath) {
  const absolutePath = path.resolve(dbPath);
  if (!fs.existsSync(absolutePath) || fs.statSync(absolutePath).size === 0) {
    return inspectDatabaseFile(absolutePath, { full: true });
  }
  let db;
  try {
    db = new DatabaseSync(absolutePath);
    db.exec('PRAGMA busy_timeout = 5000;');
    db.prepare('PRAGMA wal_checkpoint(TRUNCATE)').all();
  } finally {
    try { db?.close(); } catch {}
  }
  return inspectDatabaseFile(absolutePath, { full: true });
}

export function createVacuumBackup(dbPath, destinationPath) {
  const absolutePath = path.resolve(dbPath);
  const destination = path.resolve(destinationPath);
  const health = inspectDatabaseFile(absolutePath, { full: true });
  if (health.status !== 'healthy') {
    const error = new Error(`Database backup refused because integrity status is ${health.status}: ${health.details}`);
    error.code = 'ONLYCOIN_DB_NOT_HEALTHY';
    error.health = health;
    throw error;
  }
  fs.mkdirSync(path.dirname(destination), { recursive: true });
  if (fs.existsSync(destination)) fs.rmSync(destination, { force: true });

  let db;
  try {
    db = new DatabaseSync(absolutePath);
    db.exec('PRAGMA busy_timeout = 5000;');
    db.prepare('PRAGMA wal_checkpoint(TRUNCATE)').all();
    const escaped = destination.replaceAll("'", "''");
    db.exec(`VACUUM INTO '${escaped}'`);
  } finally {
    try { db?.close(); } catch {}
  }

  const backupHealth = inspectDatabaseFile(destination, { full: true });
  if (backupHealth.status !== 'healthy') {
    fs.rmSync(destination, { force: true });
    const error = new Error(`Backup verification failed: ${backupHealth.details}`);
    error.code = 'ONLYCOIN_BACKUP_VERIFY_FAILED';
    throw error;
  }
  return { destination, health: backupHealth };
}
