import type Database from 'better-sqlite3';
import type { CachedTool } from './tool-adapter.js';
import { logger } from '../logger.js';

// Remote MCP tool names commonly use camelCase (e.g. NocoDB's `createRecords`),
// so allow uppercase. Only the combined `mcp__id__name` must satisfy the LLM
// tool-name grammar, which also permits `[a-zA-Z0-9_-]`.
const TOOL_NAME = /^[a-zA-Z0-9_-]{1,64}$/;

interface Row {
  server_id: string;
  tool_name: string;
  description: string | null;
  input_schema: string | null;
  refreshed_at: string;
}

export function createToolCache(db: Database.Database, ttlSeconds: number) {
  return {
    getForServer(serverId: string): CachedTool[] {
      const rows = db
        .prepare('SELECT * FROM mcp_server_tools WHERE server_id = ?')
        .all(serverId) as Row[];
      return rows.map((r) => ({
        serverId: r.server_id,
        toolName: r.tool_name,
        description: r.description,
        inputSchema: r.input_schema,
      }));
    },
    getAllForServers(serverIds: string[]): CachedTool[] {
      if (serverIds.length === 0) return [];
      const placeholders = serverIds.map(() => '?').join(',');
      const rows = db
        .prepare(`SELECT * FROM mcp_server_tools WHERE server_id IN (${placeholders})`)
        .all(...serverIds) as Row[];
      return rows.map((r) => ({
        serverId: r.server_id,
        toolName: r.tool_name,
        description: r.description,
        inputSchema: r.input_schema,
      }));
    },
    get(serverId: string, toolName: string): { description: string | null; input_schema: string | null } | null {
      const row = db
        .prepare('SELECT description, input_schema FROM mcp_server_tools WHERE server_id=? AND tool_name=?')
        .get(serverId, toolName) as { description: string | null; input_schema: string | null } | undefined;
      return row ?? null;
    },
    isFreshForServer(serverId: string): boolean {
      const row = db
        .prepare('SELECT MIN(refreshed_at) AS oldest FROM mcp_server_tools WHERE server_id = ?')
        .get(serverId) as { oldest: string | null };
      if (!row.oldest) return false;
      const ageMs = Date.now() - new Date(row.oldest).getTime();
      return ageMs < ttlSeconds * 1000;
    },
    replaceForServer(
      serverId: string,
      tools: Array<{ name: string; description?: string; inputSchema?: unknown }>,
    ): void {
      const tx = db.transaction(() => {
        db.prepare('DELETE FROM mcp_server_tools WHERE server_id = ?').run(serverId);
        const insert = db.prepare(
          `INSERT INTO mcp_server_tools (server_id, tool_name, description, input_schema, refreshed_at)
           VALUES (?, ?, ?, ?, datetime('now'))`,
        );
        for (const t of tools) {
          if (!TOOL_NAME.test(t.name)) {
            logger.warn(`[mcp:cache] skip tool with invalid name: ${t.name}`);
            continue;
          }
          insert.run(
            serverId,
            t.name,
            t.description ?? null,
            t.inputSchema ? JSON.stringify(t.inputSchema) : null,
          );
        }
      });
      tx();
      logger.info(`[mcp:cache] refreshed server=${serverId} count=${tools.length}`);
    },
    invalidateServer(serverId: string): void {
      db.prepare('DELETE FROM mcp_server_tools WHERE server_id = ?').run(serverId);
    },
  };
}

export type McpToolCache = ReturnType<typeof createToolCache>;
