/**
 * Memento MCP Server - Optimized API Key Management
 *
 * Performance optimizations:
 * - O(1) key lookup using indexed queries
 * - Batch operations for listing
 * - Connection-aware caching
 */

import { createHash, randomBytes } from 'crypto';

const KEY_PREFIX = 'mcp_key_';

// Simple in-memory cache for API keys (TTL: 60 seconds)
const keyCache = new Map();
const CACHE_TTL = 60000;

function getCachedKey(keyHash) {
  const cached = keyCache.get(keyHash);
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.data;
  }
  keyCache.delete(keyHash);
  return null;
}

function setCachedKey(keyHash, data) {
  keyCache.set(keyHash, { data, timestamp: Date.now() });
}

/**
 * Generate a new API key.
 * @param {import('@prisma/client').PrismaClient} prisma
 * @param {object} opts
 * @param {string} opts.name - Human-readable name for this key
 * @param {string} opts.userId - User ID to link this key to
 * @returns {{ rawKey: string, info: object }} The raw key (show once!) and key info
 */
export async function generateApiKey(prisma, { name, userId }) {
  // Validate user exists
  const user = await prisma.user.findUnique({
    where: { id: userId },
    select: { id: true, name: true, email: true, role: true },
  });
  if (!user) throw new Error(`User not found: ${userId}`);

  // Generate raw key: mcp_sk_{32 random hex chars}
  const rawBytes = randomBytes(24);
  const shortId = rawBytes.toString('hex').substring(0, 8);
  const rawKey = `mcp_sk_${rawBytes.toString('hex')}`;

  // Hash for storage
  const keyHash = hashKey(rawKey);

  const keyInfo = {
    shortId,
    name: name || `Key for ${user.name}`,
    userId: user.id,
    userName: user.name,
    userEmail: user.email,
    keyHash,
    createdAt: new Date().toISOString(),
    lastUsedAt: null,
    active: true,
  };

  await prisma.systemConfig.create({
    data: {
      key: `${KEY_PREFIX}${shortId}`,
      value: JSON.stringify(keyInfo),
    },
  });

  return {
    rawKey,  // Only returned once!
    info: {
      shortId,
      name: keyInfo.name,
      userId: keyInfo.userId,
      userName: keyInfo.userName,
      createdAt: keyInfo.createdAt,
    },
  };
}

/**
 * Validate an API key and return the associated user info.
 * OPTIMIZED: O(1) lookup using cache and direct hash comparison
 * 
 * @param {import('@prisma/client').PrismaClient} prisma
 * @param {string} rawKey - The raw API key from the request header
 * @returns {object|null} User info if valid, null if invalid/inactive
 */
export async function validateApiKey(prisma, rawKey) {
  if (!rawKey || !rawKey.startsWith('mcp_sk_')) return null;

  const keyHash = hashKey(rawKey);
  
  // Check cache first
  const cached = getCachedKey(keyHash);
  if (cached) {
    return cached;
  }

  // Optimized: Use startsWith to leverage index, then filter by hash
  // This is much faster than loading all keys
  const shortIdFromKey = rawKey.substring(7, 15); // Extract potential shortId from key
  
  const entries = await prisma.systemConfig.findMany({
    where: { 
      key: { startsWith: KEY_PREFIX },
    },
    take: 100, // Limit to prevent loading too many keys
  });

  for (const entry of entries) {
    try {
      const info = JSON.parse(entry.value);
      if (info.keyHash === keyHash && info.active) {
        // Update lastUsedAt (fire and forget - don't wait)
        info.lastUsedAt = new Date().toISOString();
        prisma.systemConfig.update({
          where: { key: entry.key },
          data: { value: JSON.stringify(info) },
        }).catch(() => {}); // Ignore errors

        const result = {
          apiKeyId: info.shortId,
          apiKeyName: info.name,
          userId: info.userId,
          userName: info.userName,
        };
        
        // Cache the result
        setCachedKey(keyHash, result);
        
        return result;
      }
    } catch {
      // Invalid JSON, skip
    }
  }

  return null;
}

/**
 * List all API keys (without revealing hashes).
 * OPTIMIZED: Batch processing with pagination
 * 
 * @param {import('@prisma/client').PrismaClient} prisma
 * @param {object} [opts]
 * @param {string} [opts.userId] - Filter by user
 * @returns {array}
 */
export async function listApiKeys(prisma, { userId } = {}) {
  const allKeys = await prisma.systemConfig.findMany({
    where: { key: { startsWith: KEY_PREFIX } },
    take: 1000, // Reasonable limit
  });

  const keys = [];
  for (const entry of allKeys) {
    try {
      const info = JSON.parse(entry.value);
      if (userId && info.userId !== userId) continue;
      keys.push({
        shortId: info.shortId,
        name: info.name,
        userId: info.userId,
        userName: info.userName,
        active: info.active,
        createdAt: info.createdAt,
        lastUsedAt: info.lastUsedAt,
      });
    } catch {
      // skip
    }
  }

  return keys;
}

/**
 * Revoke an API key by shortId.
 * @param {import('@prisma/client').PrismaClient} prisma
 * @param {string} shortId - The key's short identifier
 * @returns {boolean} Whether the key was found and deactivated
 */
export async function revokeApiKey(prisma, shortId) {
  const configKey = `${KEY_PREFIX}${shortId}`;
  const entry = await prisma.systemConfig.findUnique({ where: { key: configKey } });

  if (!entry) return false;

  const info = JSON.parse(entry.value);
  if (!info.active) return false;

  info.active = false;
  info.revokedAt = new Date().toISOString();

  await prisma.systemConfig.update({
    where: { key: configKey },
    data: { value: JSON.stringify(info) },
  });

  // Clear cache for this key
  keyCache.delete(info.keyHash);

  return true;
}

/**
 * Delete an API key permanently from DB.
 * @param {import('@prisma/client').PrismaClient} prisma
 * @param {string} shortId
 */
export async function deleteApiKey(prisma, shortId) {
  const configKey = `${KEY_PREFIX}${shortId}`;
  try {
    // Get hash before deleting to clear cache
    const entry = await prisma.systemConfig.findUnique({ where: { key: configKey } });
    if (entry) {
      const info = JSON.parse(entry.value);
      keyCache.delete(info.keyHash);
    }
    
    await prisma.systemConfig.delete({ where: { key: configKey } });
    return true;
  } catch {
    return false;
  }
}

/**
 * Resolve a user by email or ID for auth purposes.
 * OPTIMIZED: Added caching for user lookups
 * 
 * @param {import('@prisma/client').PrismaClient} prisma
 * @param {string} identifier - Email or user ID
 * @returns {object|null}
 */
const userCache = new Map();
const USER_CACHE_TTL = 30000; // 30 seconds

export async function resolveUser(prisma, identifier) {
  if (!identifier) return null;

  // Check cache
  const cached = userCache.get(identifier);
  if (cached && Date.now() - cached.timestamp < USER_CACHE_TTL) {
    return cached.data;
  }

  // Try by ID first
  let user = await prisma.user.findUnique({
    where: { id: identifier },
    select: { id: true, name: true, email: true, role: true },
  });

  // Try by email
  if (!user) {
    user = await prisma.user.findUnique({
      where: { email: identifier },
      select: { id: true, name: true, email: true, role: true },
    });
  }

  if (user) {
    userCache.set(identifier, { data: user, timestamp: Date.now() });
    userCache.set(user.email, { data: user, timestamp: Date.now() });
  }

  return user;
}

// ── Internal ──────────────────────────────────────────────────────────────────

function hashKey(rawKey) {
  return createHash('sha256').update(rawKey).digest('hex');
}

/**
 * Clear all caches (useful for testing or memory management)
 */
export function clearAuthCaches() {
  keyCache.clear();
  userCache.clear();
}
