import type { Command } from 'commander';

import type { MemoryAdapter } from './types.js';

/**
 * Build and return a connected MemoryAdapter from a connection string or
 * the MEMORY_DB_URL / DATABASE_URL environment variable.
 *
 * For pgvector (postgres://...) the connection string is injected into
 * DATABASE_URL so that PgVectorAdapter's internal createDb() picks it up.
 *
 * Throws with a human-readable message if no connection info is available.
 */
async function resolveAdapter(dbOption: string | undefined): Promise<MemoryAdapter> {
  const connStr = dbOption ?? process.env['MEMORY_DB_URL'] ?? process.env['DATABASE_URL'];
  if (!connStr) {
    throw new Error(
      'No database connection string provided. ' +
        'Pass --db <connection-string> or set MEMORY_DB_URL / DATABASE_URL.',
    );
  }

  // Lazy imports so the module loads cleanly without a live DB during smoke tests.
  const { createMemoryAdapter, registerMemoryAdapter } = await import('./factory.js');

  if (connStr.startsWith('postgres') || connStr.startsWith('pg')) {
    // PgVectorAdapter reads DATABASE_URL via createDb() — inject it here.
    process.env['DATABASE_URL'] = connStr;

    const { PgVectorAdapter } = await import('./adapters/pgvector.js');
    registerMemoryAdapter('pgvector', (cfg) => new PgVectorAdapter(cfg as never));
    return createMemoryAdapter({ type: 'pgvector' });
  }

  // Keyword adapter backed by pglite storage; treat connStr as a data directory.
  const { KeywordAdapter } = await import('./adapters/keyword.js');
  const { createStorageAdapter, registerStorageAdapter } = await import('@mosaicstack/storage');
  const { PgliteAdapter } = await import('@mosaicstack/storage');

  registerStorageAdapter('pglite', (cfg) => new PgliteAdapter(cfg as never));

  const storage = createStorageAdapter({ type: 'pglite', dataDir: connStr });

  registerMemoryAdapter('keyword', (cfg) => new KeywordAdapter(cfg as never));
  return createMemoryAdapter({ type: 'keyword', storage });
}

/**
 * Register `memory` subcommands on an existing Commander program.
 * Follows the registerQualityRails pattern from @mosaicstack/quality-rails.
 */
export function registerMemoryCommand(parent: Command): void {
  const memory = parent.command('memory').description('Inspect and query the Mosaic memory layer');

  // ── memory search <query> ──────────────────────────────────────────────
  memory
    .command('search <query>')
    .description('Semantic search over insights')
    .option('--db <connection-string>', 'Database connection string (or set MEMORY_DB_URL)')
    .option('--limit <n>', 'Maximum number of results', '10')
    .option('--agent <id>', 'Filter by agent / user ID')
    .action(async (query: string, opts: { db?: string; limit: string; agent?: string }) => {
      let adapter: MemoryAdapter | undefined;
      try {
        adapter = await resolveAdapter(opts.db);
        const limit = parseInt(opts.limit, 10);
        const userId = opts.agent ?? 'system';
        const results = await adapter.searchInsights(userId, query, { limit });

        if (results.length === 0) {
          console.log('No insights found.');
        } else {
          for (const r of results) {
            console.log(`[${r.id}] (score=${r.score.toFixed(3)}) ${r.content}`);
          }
        }
      } catch (err) {
        console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
        process.exitCode = 1;
      } finally {
        await adapter?.close();
      }
    });

  // ── memory stats ──────────────────────────────────────────────────────
  memory
    .command('stats')
    .description('Print memory tier info: adapter type, insight count, preference count')
    .option('--db <connection-string>', 'Database connection string (or set MEMORY_DB_URL)')
    .option('--agent <id>', 'User / agent ID scope for counts', 'system')
    .action(async (opts: { db?: string; agent: string }) => {
      let adapter: MemoryAdapter | undefined;
      try {
        adapter = await resolveAdapter(opts.db);

        const adapterType = adapter.name;

        const insightCount = await adapter
          .searchInsights(opts.agent, '', { limit: 100000 })
          .then((r) => r.length)
          .catch(() => -1);

        const prefCount = await adapter
          .listPreferences(opts.agent)
          .then((r) => r.length)
          .catch(() => -1);

        console.log(`adapter:     ${adapterType}`);
        console.log(`insights:    ${insightCount === -1 ? 'unavailable' : String(insightCount)}`);
        console.log(`preferences: ${prefCount === -1 ? 'unavailable' : String(prefCount)}`);
      } catch (err) {
        console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
        process.exitCode = 1;
      } finally {
        await adapter?.close();
      }
    });

  // ── memory insights ───────────────────────────────────────────────────
  const insightsCmd = memory.command('insights').description('Manage insights');

  insightsCmd
    .command('list')
    .description('List recent insights')
    .option('--db <connection-string>', 'Database connection string (or set MEMORY_DB_URL)')
    .option('--limit <n>', 'Maximum number of results', '20')
    .option('--agent <id>', 'User / agent ID scope', 'system')
    .action(async (opts: { db?: string; limit: string; agent: string }) => {
      let adapter: MemoryAdapter | undefined;
      try {
        adapter = await resolveAdapter(opts.db);
        const limit = parseInt(opts.limit, 10);
        const results = await adapter.searchInsights(opts.agent, '', { limit });

        if (results.length === 0) {
          console.log('No insights found.');
        } else {
          for (const r of results) {
            console.log(`[${r.id}] ${r.content}`);
          }
        }
      } catch (err) {
        console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
        process.exitCode = 1;
      } finally {
        await adapter?.close();
      }
    });

  // ── memory preferences ────────────────────────────────────────────────
  const prefsCmd = memory.command('preferences').description('Manage stored preferences');

  prefsCmd
    .command('list')
    .description('List stored preferences')
    .option('--db <connection-string>', 'Database connection string (or set MEMORY_DB_URL)')
    .option('--agent <id>', 'User / agent ID scope', 'system')
    .option('--category <cat>', 'Filter by category')
    .action(async (opts: { db?: string; agent: string; category?: string }) => {
      let adapter: MemoryAdapter | undefined;
      try {
        adapter = await resolveAdapter(opts.db);
        const prefs = await adapter.listPreferences(opts.agent, opts.category);

        if (prefs.length === 0) {
          console.log('No preferences found.');
        } else {
          for (const p of prefs) {
            console.log(`[${p.category}] ${p.key} = ${JSON.stringify(p.value)}`);
          }
        }
      } catch (err) {
        console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
        process.exitCode = 1;
      } finally {
        await adapter?.close();
      }
    });
}
