import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { DatabaseSync } from 'node:sqlite';
import { config } from './config.js';
import { writeProjectEnv } from './env.js';
import { createInitialAdmin } from './admin-auth.js';
import { setSetting, withTransaction } from './db.js';
import { testSmtpConfiguration } from './email.js';
import { nowIso, safeEqual, uuid } from './utils.js';

function fail(code, message, status = 400, details) {
  throw Object.assign(new Error(message), { code, status, details });
}

export function isInstalled() {
  return fs.existsSync(config.installLockPath);
}

export function readInstallMetadata() {
  if (!isInstalled()) return null;
  try { return JSON.parse(fs.readFileSync(config.installLockPath, 'utf8')); }
  catch { return { installed: true }; }
}

export function ensureInstallToken() {
  if (isInstalled()) return '';
  fs.mkdirSync(config.dataDir, { recursive: true });
  if (!fs.existsSync(config.installTokenPath)) {
    fs.writeFileSync(config.installTokenPath, crypto.randomBytes(24).toString('base64url'), { encoding: 'utf8', mode: 0o600 });
    try { fs.chmodSync(config.installTokenPath, 0o600); } catch { /* Windows */ }
  }
  return fs.readFileSync(config.installTokenPath, 'utf8').trim();
}

export function verifyInstallToken(value) {
  if (isInstalled()) fail('ALREADY_INSTALLED', 'OnlyCoin kurulumu daha önce tamamlanmış.', 409);
  const expected = ensureInstallToken();
  const supplied = String(value || '').trim();
  if (!supplied || !safeEqual(supplied, expected)) fail('INVALID_INSTALL_TOKEN', 'Kurulum anahtarı geçersiz.', 401);
  return true;
}

function canWrite(target) {
  try {
    fs.mkdirSync(target, { recursive: true });
    const probe = path.join(target, `.onlycoin-write-${process.pid}-${Date.now()}`);
    fs.writeFileSync(probe, 'ok', { mode: 0o600 });
    fs.rmSync(probe, { force: true });
    return true;
  } catch { return false; }
}

export function installationRequirements() {
  const [major, minor] = process.versions.node.split('.').map(Number);
  const envDirWritable = canWrite(config.rootDir);
  const dataWritable = canWrite(config.dataDir);
  let sqliteVersion = '';
  try {
    const probe = new DatabaseSync(':memory:');
    sqliteVersion = String(probe.prepare('SELECT sqlite_version() AS version').get().version);
    probe.close();
  } catch { sqliteVersion = ''; }
  const checks = [
    { id: 'node', label: 'Node.js 22.16+', ok: major > 22 || (major === 22 && minor >= 16), value: process.versions.node },
    { id: 'sqlite', label: 'SQLite motoru', ok: Boolean(sqliteVersion), value: sqliteVersion || 'Kullanılamıyor' },
    { id: 'data', label: 'Veri dizini yazılabilir', ok: dataWritable, value: config.dataDir },
    { id: 'env', label: 'Yapılandırma dizini yazılabilir', ok: envDirWritable, value: config.rootDir },
    { id: 'crypto', label: 'Güvenli rastgele sayı üreteci', ok: typeof crypto.randomBytes === 'function', value: 'AES-256-GCM / scrypt' }
  ];
  return { ok: checks.every((item) => item.ok), checks };
}

export function installerStatus() {
  const installed = isInstalled();
  return {
    installed,
    version: config.appVersion,
    requirements: installationRequirements(),
    installation: installed ? readInstallMetadata() : null,
    requiresToken: !installed
  };
}

function validEmail(value, label = 'E-posta') {
  const email = String(value || '').trim().toLowerCase();
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) fail('INVALID_EMAIL', `${label} geçersiz.`, 400);
  return email;
}

function validOrigin(value) {
  let url;
  try { url = new URL(String(value || '').trim()); }
  catch { fail('INVALID_APP_ORIGIN', 'Site adresi geçerli bir http veya https URL olmalıdır.', 400); }
  if (!['http:', 'https:'].includes(url.protocol)) fail('INVALID_APP_ORIGIN', 'Site adresi http veya https kullanmalıdır.', 400);
  const local = ['localhost', '127.0.0.1', '::1'].includes(url.hostname);
  if (url.protocol !== 'https:' && !local) fail('HTTPS_REQUIRED', 'Canlı kurulumda site adresi HTTPS kullanmalıdır.', 400);
  url.pathname = '/';
  url.search = '';
  url.hash = '';
  return url.toString().replace(/\/$/, '');
}

function smtpOptions(input = {}) {
  const port = Number(input.port || 587);
  if (!Number.isInteger(port) || port < 1 || port > 65535) fail('INVALID_SMTP_PORT', 'SMTP portu geçersiz.', 400);
  const host = String(input.host || '').trim();
  if (!host) fail('SMTP_HOST_REQUIRED', 'SMTP sunucusu zorunludur.', 400);
  return {
    host,
    port,
    secure: Boolean(input.secure),
    startTls: input.startTls == null ? !Boolean(input.secure) : Boolean(input.startTls),
    username: String(input.username || '').trim(),
    password: String(input.password || ''),
    fromEmail: validEmail(input.fromEmail, 'Gönderen e-posta'),
    fromName: String(input.fromName || 'OnlyCoin').trim().slice(0, 100),
    ehloName: String(input.ehloName || 'onlycoin.local').trim().slice(0, 120)
  };
}

export async function testInstallerSmtp(input = {}, installToken = '') {
  verifyInstallToken(installToken);
  const options = smtpOptions(input.smtp || input);
  const recipient = validEmail(input.recipient || input.adminEmail || options.fromEmail, 'Test alıcısı');
  await testSmtpConfiguration(options, recipient);
  return { ok: true, recipient, message: 'SMTP bağlantısı ve test e-postası başarıyla doğrulandı.' };
}

export async function completeInstallation(db, input = {}, installToken = '', ip = '') {
  verifyInstallToken(installToken);
  const requirements = installationRequirements();
  if (!requirements.ok) fail('REQUIREMENTS_FAILED', 'Sunucu gereksinimleri tamamlanmadan kurulum yapılamaz.', 409, requirements);

  const site = input.site || {};
  const admin = input.admin || {};
  const smtp = smtpOptions(input.smtp || {});
  const appOrigin = validOrigin(site.appOrigin);
  const adminEmail = validEmail(admin.email, 'Yönetici e-posta');
  if (adminEmail !== validEmail(input.smtpTestRecipient || adminEmail, 'SMTP test alıcısı')) {
    fail('SMTP_RECIPIENT_MISMATCH', 'Kurulum testi yönetici e-posta adresine gönderilmelidir.', 400);
  }
  await testSmtpConfiguration(smtp, adminEmail);

  const installationId = uuid();
  const now = nowIso();
  const appName = String(site.appName || 'OnlyCoin').trim().slice(0, 80) || 'OnlyCoin';
  const companyName = String(site.companyName || '').trim().slice(0, 160);
  const supportEmail = validEmail(site.supportEmail || adminEmail, 'Destek e-posta');
  const timezone = String(site.timezone || 'Europe/Istanbul').trim().slice(0, 80);
  const authSecret = crypto.randomBytes(64).toString('base64url');

  // Retrying a failed, uncommitted installation is safe: no public installation
  // lock exists and possession of the one-time installer token is required.
  withTransaction(db, () => {
    db.prepare('DELETE FROM admin_users').run();
  });
  const createdAdmin = createInitialAdmin(db, { ...admin, email: adminEmail }, ip);

  const settings = {
    app_name: appName,
    company_legal_name: companyName,
    support_email: supportEmail,
    privacy_contact_email: supportEmail,
    timezone,
    site_enabled: true,
    account_registration_enabled: true,
    email_registration_enabled: true,
    account_require_email_verification: true,
    email_verification_required: true,
    smtp_enabled: true,
    smtp_host: smtp.host,
    smtp_port: smtp.port,
    smtp_secure: smtp.secure,
    smtp_starttls: smtp.startTls,
    smtp_username: smtp.username,
    smtp_from_email: smtp.fromEmail,
    smtp_from_name: smtp.fromName,
    smtp_ehlo_name: smtp.ehloName,
    initial_points: 0,
    conversion_enabled: false,
    purchases_enabled: false,
    fiat_payments_enabled: false,
    bank_transfer_enabled: false,
    crypto_funding_enabled: false,
    crypto_payment_for_goods_enabled: false,
    presale_enabled: false,
    deposit_enabled: false,
    withdrawal_enabled: false,
    distribution_enabled: false,
    distribution_auto_start: false,
    distribution_signer_mode: 'webhook',
    login_cookie_enabled: true,
    installation_id: installationId,
    installed_at: now
  };
  withTransaction(db, () => {
    const statement = db.prepare(`INSERT INTO settings(key,value,updated_at) VALUES(?,?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value,updated_at=excluded.updated_at`);
    for (const [key, value] of Object.entries(settings)) statement.run(key, JSON.stringify(value), now);
    db.prepare(`INSERT INTO installation_events(id,installation_id,event_type,metadata_json,created_at) VALUES(?,?,?,?,?)`)
      .run(uuid(), installationId, 'installation.completed', JSON.stringify({ appName, appOrigin, adminId: createdAdmin.id, adminEmail }), now);
  });

  writeProjectEnv({
    APP_VERSION: config.appVersion,
    NODE_ENV: 'production',
    HOST: process.env.HOST || '0.0.0.0',
    PORT: process.env.PORT || String(config.port),
    APP_ORIGIN: appOrigin,
    DATA_DIR: process.env.DATA_DIR || './data',
    TRUST_PROXY: process.env.TRUST_PROXY || 'false',
    AUTH_SECRET: authSecret,
    INSTALLATION_ID: installationId,
    PUBLIC_URL: appOrigin,
    SMTP_PASSWORD: smtp.password,
    TELEGRAM_BOT_TOKEN: '',
    TELEGRAM_WEBHOOK_SECRET: '',
    EVM_RPC_URL: '',
    BITCOIN_RPC_URL: '',
    BITCOIN_RPC_USER: '',
    BITCOIN_RPC_PASSWORD: ''
  });

  const metadata = {
    installed: true,
    installationId,
    version: config.appVersion,
    installedAt: now,
    appOrigin,
    appName,
    adminEmail
  };
  fs.mkdirSync(config.dataDir, { recursive: true });
  const temporary = `${config.installLockPath}.${process.pid}.tmp`;
  fs.writeFileSync(temporary, JSON.stringify(metadata, null, 2), { encoding: 'utf8', mode: 0o600 });
  fs.renameSync(temporary, config.installLockPath);
  try { fs.chmodSync(config.installLockPath, 0o600); } catch { /* Windows */ }
  fs.rmSync(config.installTokenPath, { force: true });

  return { ok: true, installation: metadata, admin: createdAdmin, restartRequired: true };
}
