/**
 * Database query caching with Redis
 * Implements query result caching with automatic invalidation
 * 
 * Note: This module uses optional dynamic import for @the-order/cache
 * to avoid requiring it as a direct dependency. If cache is not available,
 * queries will execute directly without caching.
 */

import { query } from './client';
import type { QueryResult, QueryResultRow } from './client';

export interface CacheOptions {
  ttl?: number; // Time to live in seconds
  keyPrefix?: string;
  enabled?: boolean;
}

// Cache client interface (matches @the-order/cache API)
// This interface allows us to use the cache without a compile-time dependency
interface CacheClient {
  get<T>(key: string): Promise<T | null>;
  set(key: string, value: unknown, ttl?: number): Promise<void>;
  delete(key: string): Promise<void>;
  invalidate(pattern: string): Promise<number>;
}

// Cache client instance (lazy-loaded via dynamic import)
let cacheClientPromise: Promise<CacheClient | null> | null = null;

/**
 * Get cache client (lazy-loaded via dynamic import)
 * Returns null if cache module is not available
 */
async function getCacheClient(): Promise<CacheClient | null> {
  if (cacheClientPromise === null) {
    cacheClientPromise = (async () => {
      try {
        // Use dynamic import with a string literal that TypeScript can't resolve at compile time
        // This is done by constructing the import path dynamically
        const cacheModulePath = '@the-order/cache';
        // eslint-disable-next-line @typescript-eslint/no-implied-eval
        const importFunc = new Function('specifier', 'return import(specifier)');
        const cacheModule = await importFunc(cacheModulePath);
        return cacheModule.getCacheClient() as CacheClient;
      } catch {
        // Cache module not available - caching will be disabled
        return null;
      }
    })();
  }

  return cacheClientPromise;
}

/**
 * Execute a query with caching
 */
export async function cachedQuery<T extends QueryResultRow = QueryResultRow>(
  sql: string,
  params?: unknown[],
  options: CacheOptions = {}
): Promise<QueryResult<T>> {
  const { ttl = 3600, keyPrefix = 'db:query:', enabled = true } = options;

  if (!enabled) {
    return query<T>(sql, params);
  }

  const cache = await getCacheClient();
  if (!cache) {
    // Cache not available - execute query directly
    return query<T>(sql, params);
  }

  const cacheKey = `${keyPrefix}${sql}:${JSON.stringify(params || [])}`;

  // Try to get from cache
  const cached = await cache.get<QueryResult<T>>(cacheKey);
  if (cached) {
    return cached;
  }

  // Execute query
  const result = await query<T>(sql, params);

  // Cache result
  await cache.set(cacheKey, result, ttl);

  return result;
}

/**
 * Invalidate cache for a pattern
 */
export async function invalidateCache(pattern: string): Promise<number> {
  const cache = await getCacheClient();
  if (!cache) {
    return 0;
  }
  return cache.invalidate(`db:query:${pattern}*`);
}

/**
 * Invalidate cache for a specific query
 */
export async function invalidateQueryCache(sql: string, params?: unknown[]): Promise<void> {
  const cache = await getCacheClient();
  if (!cache) {
    return;
  }
  const cacheKey = `db:query:${sql}:${JSON.stringify(params || [])}`;
  await cache.delete(cacheKey);
}

/**
 * Cache decorator for database functions
 * Note: This is a simplified implementation. In production, you'd need to
 * extract SQL and params from the function or pass them as metadata.
 */
export function cached<T extends (...args: unknown[]) => Promise<QueryResult<QueryResultRow>>>(
  fn: T
): T {
  return (async (...args: Parameters<T>) => {
    const result = await fn(...args);
    return result;
  }) as T;
}
