import { mkdirSync, existsSync } from 'fs';
import { dirname } from 'path';
import { resolveDbAbsolutePath, isDev, DEV_AUTOSEED_DB, DEV_DEFAULT_USER } from './env';

function applyDefaultPragmas(instance: any): void {
  try {
    instance.exec(`PRAGMA busy_timeout = 5000;`);
    // Intentar activar WAL (si no es soportado, SQLite devolverá 'memory' u otro modo)
    try {
      if (typeof instance.query === 'function') {
        instance.query(`PRAGMA journal_mode = WAL`)?.get?.();
      } else {
        instance.prepare?.(`PRAGMA journal_mode = WAL`)?.get?.();
      }
    } catch {}
    instance.exec(`PRAGMA synchronous = NORMAL;`);
    instance.exec(`PRAGMA wal_autocheckpoint = 1000;`);
    // Asegurar claves foráneas siempre activas
    instance.exec(`PRAGMA foreign_keys = ON;`);
  } catch (e) {
    console.warn('[web/db] No se pudieron aplicar PRAGMAs (WAL, busy_timeout...):', e);
  }
}

/**
 * Intenta cargar un constructor de Database compatible:
 * - En Bun (SSR nativo): bun:sqlite
 * - En Node (Vite dev SSR): better-sqlite3
 */
async function importSqliteDatabase(): Promise<any> {
  // En tests, forzar bun:sqlite para evitar mezclar engines con la conexión de tests
  const nodeEnv = String(process.env.NODE_ENV || '').toLowerCase();
  if (nodeEnv === 'test') {
    const mod: any = await import('bun:sqlite');
    return (mod as any).Database || (mod as any).default || mod;
  }

  // En desarrollo (Vite SSR), cargar better-sqlite3 vía require de Node para mantener el contexto CJS
  if (typeof import.meta !== 'undefined' && (import.meta as any).env?.DEV) {
    const modModule: any = await import('node:module');
    const require = modModule.createRequire(import.meta.url);
    const mod = require('better-sqlite3');
    return (mod as any).default || (mod as any).Database || mod;
  }

  // En producción (Bun en runtime), usar bun:sqlite nativo
  const mod: any = await import('bun:sqlite');
  return (mod as any).Database || (mod as any).default || mod;
}

/**
 * Abre la BD compartida. En desarrollo, si el archivo no existe y DEV_AUTOSEED_DB=true,
 * inicializa el esquema (migraciones) y siembra datos de demo.
 * Nota: usa bun:sqlite si está disponible; en SSR Node usa better-sqlite3.
 */
async function openDb(filename: string = 'tasks.db'): Promise<any> {
  const absolutePath = resolveDbAbsolutePath(filename);
  const firstCreate = !existsSync(absolutePath);

  // Crear directorio padre si no existe
  try {
    mkdirSync(dirname(absolutePath), { recursive: true });
  } catch (err: any) {
    if (err?.code !== 'EEXIST') throw err;
  }

  const DatabaseCtor = await importSqliteDatabase();
  const instance = new DatabaseCtor(absolutePath);
  applyDefaultPragmas(instance);

  // Auto-inicialización de esquema en desarrollo si falta y seed opcional
  if (isDev()) {
    // ¿Existe la tabla principal?
    let hasTasksTable = false;
    try {
      instance.prepare(`SELECT 1 FROM tasks LIMIT 1`).get();
      hasTasksTable = true;
    } catch {}

    // Si no existe el esquema, aplicar inicialización/migraciones
    if (!hasTasksTable) {
      const isBun = typeof (globalThis as any).Bun !== 'undefined';

      if (isBun) {
        // En Bun podemos reutilizar initializeDatabase del repo principal
        try {
          const dbModule = await import('../../../../../src/db');
          if (typeof (dbModule as any).initializeDatabase === 'function') {
            (dbModule as any).initializeDatabase(instance);
            hasTasksTable = true;
            console.info('[web/db] DEV: esquema inicializado (Bun initializeDatabase).');
          }
        } catch (e) {
          console.warn('[web/db] No se pudo ejecutar initializeDatabase en dev (Bun):', e);
        }
      } else {
        // En SSR Node: aplicar migraciones directamente con compat para .query
        try {
          const mod = await import('../../../../../src/db/migrations/index.ts');
          const list = (mod as any).migrations as any[];
          const compat: any = instance;
          if (typeof compat.query !== 'function') {
            compat.query = (sql: string) => ({
              all: () => compat.prepare(sql).all(),
              get: () => compat.prepare(sql).get()
            });
          }
          try { compat.exec?.(`PRAGMA foreign_keys = ON;`); } catch {}
          for (const m of list) {
            try {
              await (m.up as any)(compat);
            } catch (e) {
              console.warn('[web/db] Error aplicando migración en dev (Node):', (m as any)?.name ?? '(sin nombre)', e);
            }
          }
          // Verificar de nuevo
          try {
            compat.prepare(`SELECT 1 FROM tasks LIMIT 1`).get();
            hasTasksTable = true;
            console.info('[web/db] DEV: esquema inicializado (migraciones aplicadas en Node).');
          } catch {}
        } catch (e) {
          console.warn('[web/db] No se pudieron aplicar migraciones en dev (Node):', e);
        }
      }
    }

    // Seed de datos de demo si la tabla está vacía (por defecto habilitado en dev)
    try {
      let count = 0;
      try {
        const row = instance.prepare(`SELECT COUNT(1) AS c FROM tasks`).get() as any;
        count = Number(row?.c ?? 0);
      } catch {
        // Si aún no existe la tabla, no seedear
        count = 0;
      }

      const shouldSeed = (typeof DEV_AUTOSEED_DB === 'boolean' ? DEV_AUTOSEED_DB : true);
      if (count === 0 && shouldSeed) {
        console.info('[web/db] DEV: tabla tasks vacía; iniciando seed de demo...');
        try {
          const seed = await import('./dev-seed');
          if (typeof (seed as any).seedDev === 'function') {
            await (seed as any).seedDev(instance, DEV_DEFAULT_USER);
            console.info('[web/db] DEV: seed de demo completado.');
          } else {
            console.warn('[web/db] DEV: módulo dev-seed sin función seedDev; omitiendo seed.');
          }
        } catch (e) {
          console.warn('[web/db] DEV: no se pudo cargar/ejecutar dev-seed; omitiendo seed. Error:', e);
        }
      } else {
        console.info(`[web/db] DEV: seed no aplicado (count=${count}, DEV_AUTOSEED_DB=${shouldSeed}).`);
      }
    } catch (e) {
      console.warn('[web/db] DEV: error al evaluar seed de demo:', e);
    }
  }

  return instance;
}

let _db: any | null = null;

/**
 * Devuelve una única instancia compartida (lazy) de la BD.
 */
export async function getDb(filename: string = 'tasks.db'): Promise<any> {
  if (_db) return _db;
  _db = await openDb(filename);
  return _db;
}

/**
 * Cierra y resetea la instancia compartida (útil en tests para evitar manejar
 * un descriptor abierto al borrar el archivo de la BD en disco).
 */
export function closeDb(): void {
  try {
    if (_db && typeof _db.close === 'function') {
      _db.close();
    }
  } catch {}
  _db = null;
}
