import { createEmbeddingProvider, resolveEmbeddingConfigFromEnv } from './embeddings.js';
import { Indexer, createQdrantClient } from './indexer.js';
import { Searcher } from './searcher.js';
import { WatcherManager } from './watcher.js';
import { MetadataStore } from './metadata-db.js';
import { createTreeSitterManager } from './tree-sitter-parser.js';
import type { TreeSitterManager } from './tree-sitter-parser.js';
import type { ProjectConfig } from './types.js';
import { createApp, refreshGaugeMetrics } from './app.js';
import { QueryCache } from './query-cache.js';
import { createMetrics } from './metrics.js';
import { buildTelemetry } from './telemetry/factory.js';
import { tctx, systemContext } from './telemetry/context.js';
import { scheduleRetention } from './telemetry/retention.js';

// ── State ──────────────────────────────────────────────────────────────────

/** Registered projects grouped by group name */
const projectsByGroup = new Map<string, ProjectConfig[]>();

// ── Bootstrap ──────────────────────────────────────────────────────────────

const PORT = parseInt(process.env.PORT ?? '9876', 10);
const QDRANT_URL = process.env.QDRANT_URL ?? 'http://localhost:6333';
const QDRANT_API_KEY = process.env.QDRANT_API_KEY || undefined;
const OLLAMA_URL = process.env.OLLAMA_URL ?? 'http://127.0.0.1:11434';
const PAPARATS_PROJECTS = process.env.PAPARATS_PROJECTS
  ? process.env.PAPARATS_PROJECTS.split(',')
      .map((p) => p.trim())
      .filter(Boolean)
  : undefined;

const embeddingConfig = resolveEmbeddingConfigFromEnv();
const embeddingProvider = createEmbeddingProvider(embeddingConfig);
console.log(
  `[startup] Embedding provider: ${embeddingConfig.provider} (${embeddingConfig.model}, ${embeddingConfig.dimensions}d)`
);

if (OLLAMA_URL !== 'http://127.0.0.1:11434') {
  process.env.OLLAMA_URL = OLLAMA_URL;
}

const metadataStore = new MetadataStore();

let treeSitter: TreeSitterManager | undefined;
try {
  treeSitter = await createTreeSitterManager();
  console.log('[startup] Tree-sitter WASM initialized');
} catch (err) {
  console.warn(
    `[startup] Tree-sitter initialization failed (non-fatal): ${(err as Error).message}`
  );
}

const qdrantClient = createQdrantClient({ url: QDRANT_URL, apiKey: QDRANT_API_KEY });
const queryCache = new QueryCache();
const metrics = await createMetrics();
const { telemetry, analytics } = await buildTelemetry();
embeddingProvider.attachTelemetry(telemetry);
embeddingProvider.attachMetrics(metrics);
const stopRetention = analytics ? scheduleRetention(analytics) : null;

const indexer = new Indexer({
  qdrantUrl: QDRANT_URL,
  embeddingProvider,
  dimensions: embeddingProvider.dimensions,
  metadataStore,
  treeSitter,
  qdrantClient,
  telemetry,
  metrics,
});

const searcher = new Searcher({
  qdrantUrl: QDRANT_URL,
  embeddingProvider,
  qdrantClient,
  cache: queryCache,
  metrics,
  allowedProjects: PAPARATS_PROJECTS,
  telemetry,
  analytics: analytics ?? undefined,
});

const watcherManager = new WatcherManager({
  onFileChanged: async (groupName, project, filePath) => {
    await tctx.run(systemContext('watcher'), async () => {
      searcher.invalidateGroupCache(groupName);
      metrics.incWatcherEventsTotal(groupName, 'changed');
      await indexer.updateFile(groupName, project, filePath);
    });
  },
  onFileDeleted: async (groupName, project, filePath) => {
    await tctx.run(systemContext('watcher'), async () => {
      searcher.invalidateGroupCache(groupName);
      metrics.incWatcherEventsTotal(groupName, 'deleted');
      await indexer.deleteFile(groupName, project, filePath);
    });
  },
});

// ── Create and start server ─────────────────────────────────────────────────

const { app, mcpHandler, setShuttingDown, getShuttingDown, stopGroupPoll } = createApp({
  searcher,
  indexer,
  watcherManager,
  embeddingProvider,
  projectsByGroup,
  metadataStore,
  metrics,
  telemetry,
  analytics: analytics ?? undefined,
});

const GAUGE_REFRESH_INTERVAL_MS = 15_000;
let gaugeRefreshTimer: NodeJS.Timeout | undefined;
if (metrics.enabled) {
  gaugeRefreshTimer = setInterval(() => {
    refreshGaugeMetrics({ metrics, indexer, searcher, embeddingProvider }).catch((err) => {
      console.warn(`[metrics] gauge refresh failed (non-fatal): ${(err as Error).message}`);
    });
  }, GAUGE_REFRESH_INTERVAL_MS);
  gaugeRefreshTimer.unref();
}

const server = app.listen(PORT, '0.0.0.0', () => {
  console.log(`paparats-mcp listening on http://0.0.0.0:${PORT}`);
  console.log(`  MCP Coding:               http://localhost:${PORT}/mcp`);
  console.log(`  MCP Support:              http://localhost:${PORT}/support/mcp`);
  console.log(`  MCP SSE (legacy):         http://localhost:${PORT}/sse`);
  console.log(`  Health:                   http://localhost:${PORT}/health`);
  console.log(`  Stats:                    http://localhost:${PORT}/api/stats`);
  console.log(
    `  Qdrant:                   ${QDRANT_URL}${QDRANT_API_KEY ? ' (authenticated)' : ''}`
  );
  console.log(`  Ollama:                   ${OLLAMA_URL}`);
  if (metrics.enabled) {
    console.log(`  Metrics:                  http://localhost:${PORT}/metrics`);
  }
  if (PAPARATS_PROJECTS?.length) {
    console.log(`  Project scope:            ${PAPARATS_PROJECTS.join(', ')}`);
    const slashProjects = PAPARATS_PROJECTS.filter((p) => p.includes('/'));
    if (slashProjects.length > 0) {
      console.warn(
        `  ⚠ PAPARATS_PROJECTS contains org/repo-style names: ${slashProjects.join(', ')}`
      );
      console.warn(`    Project names are directory basenames (e.g. "billing" not "org/billing").`);
      console.warn(`    Check your PAPARATS_PROJECTS values match actual indexed project names.`);
    }
  }
});

server.on('error', (err: NodeJS.ErrnoException) => {
  if (err.code === 'EADDRINUSE') {
    console.error(`\nError: Port ${PORT} is already in use`);
    console.error('Set a different PORT environment variable or stop the other process\n');
    process.exit(1);
  } else {
    console.error('Server error:', err);
    process.exit(1);
  }
});

// ── Graceful shutdown ──────────────────────────────────────────────────────

async function shutdown(): Promise<void> {
  if (getShuttingDown()) return;
  setShuttingDown(true);

  console.log('\nShutting down gracefully...');

  await new Promise<void>((resolve) => {
    server.close((err) => {
      if (err) console.error('Error closing server:', err);
      resolve();
    });
    // Force-resolve after 5s if connections don't drain
    setTimeout(resolve, 5000);
  });

  if (gaugeRefreshTimer) clearInterval(gaugeRefreshTimer);
  stopGroupPoll();
  mcpHandler.destroy();
  await watcherManager.stopAll();
  embeddingProvider.close();
  metadataStore.close();
  treeSitter?.close();
  stopRetention?.();
  analytics?.close();
  telemetry.close();

  process.exit(0);
}

process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);

// ── Re-exports for programmatic use (from lib.ts) ─────────────────────────
export * from './lib.js';
