import Database from 'better-sqlite3';
import type BetterSqlite3 from 'better-sqlite3';
import path from 'path';
import fs from 'fs';
import crypto from 'crypto';

let db: BetterSqlite3.Database | null = null;
let insertStmt: BetterSqlite3.Statement | null = null;
const runtimeAnalyticsSalt = crypto.randomBytes(32).toString('hex');

function getAnalyticsSalt() {
  return process.env.ANALYTICS_SALT || runtimeAnalyticsSalt;
}

function getDbPath() {
  if (process.env.VISIT_DB_PATH) {
    return process.env.VISIT_DB_PATH;
  }

  const serverStorage = '/server_storage';
  try {
    fs.mkdirSync(serverStorage, { recursive: true, mode: 0o700 });
    fs.accessSync(serverStorage, fs.constants.W_OK);
    return path.join(serverStorage, 'visitors.db');
  } catch {
    const localStorage = path.join(process.cwd(), '.data');
    fs.mkdirSync(localStorage, { recursive: true, mode: 0o700 });
    return path.join(localStorage, 'visitors.db');
  }
}

function createVisitorId(clientAddress: string, userAgent: string) {
  const day = new Date().toISOString().slice(0, 10);
  return crypto
    .createHmac('sha256', getAnalyticsSalt())
    .update(`${day}:${clientAddress}:${userAgent}`)
    .digest('hex')
    .slice(0, 32);
}

function ensureSchema(database: BetterSqlite3.Database) {
  database.exec(`
    CREATE TABLE IF NOT EXISTS visits (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      visitor_id TEXT NOT NULL,
      path TEXT NOT NULL,
      visited_at TEXT NOT NULL DEFAULT (datetime('now'))
    );
  `);

  const columns = database
    .prepare("PRAGMA table_info('visits')")
    .all() as { name: string; notnull: number }[];
  const columnNames = new Set(columns.map((column) => column.name));

  if (!columnNames.has('visitor_id')) {
    database.exec("ALTER TABLE visits ADD COLUMN visitor_id TEXT NOT NULL DEFAULT 'legacy'");
    columnNames.add('visitor_id');
  }

  if (!columnNames.has('path')) {
    throw new Error('visits table is missing required path column');
  }

  return {
    hasLegacyIpAddress: columnNames.has('ip_address'),
  };
}

function getDb() {
  if (db) return db;

  try {
    const dbPath = getDbPath();
    db = new Database(dbPath);
    db.pragma('busy_timeout = 5000');
    db.pragma('journal_mode = WAL');
    db.pragma('synchronous = NORMAL');

    const schema = ensureSchema(db);

    insertStmt = schema.hasLegacyIpAddress
      ? db.prepare("INSERT INTO visits (ip_address, visitor_id, path) VALUES ('redacted', ?, ?)")
      : db.prepare('INSERT INTO visits (visitor_id, path) VALUES (?, ?)');

    return db;
  } catch (e) {
    console.error('Failed to initialize SQLite database:', e);
    db = null;
    insertStmt = null;
    return null;
  }
}

export function logVisit(clientAddress: string, userAgent: string, visitPath: string) {
  const database = getDb();
  if (!database || !insertStmt) return null;

  const visitorId = createVisitorId(clientAddress, userAgent);
  insertStmt.run(visitorId, visitPath);
  return visitorId;
}
