import { Redis } from "@upstash/redis";

/**
 * Global Redis Client
 * Configured for Upstash Serverless REST API (optimized for Next.js App Router on Vercel)
 */
const upstashUrl = process.env.UPSTASH_REDIS_REST_URL;
const upstashToken = process.env.UPSTASH_REDIS_REST_TOKEN;

export const redis = (upstashUrl && upstashToken)
  ? new Redis({
      url: upstashUrl,
      token: upstashToken,
    })
  : null;

/**
 * Standard TTL Presets (in seconds)
 */
export const CACHE_TTL = {
  SHORT: 60,            // 1 minute (e.g. dynamic metrics, live counts)
  MEDIUM: 300,          // 5 minutes (e.g. user sessions, entitlements)
  LONG: 900,           // 15 minutes (e.g. property details, agent profiles)
  EXTENDED: 3600,       // 1 hour (e.g. trending locations, catalog queries)
  STATIC: 86400,        // 24 hours (e.g. Nigerian geo taxonomy, states/LGAs)
};

/**
 * Safe Redis Cache Get Helper with Graceful Degrade
 */
export async function getCachedJson<T>(key: string): Promise<T | null> {
  if (!redis) return null;
  try {
    const data = await redis.get<T>(key);
    return data ?? null;
  } catch (error) {
    console.warn(`[Redis Cache Warning] Failed to get key "${key}":`, error);
    return null;
  }
}

/**
 * Safe Redis Cache Set Helper with Graceful Degrade
 */
export async function setCachedJson<T>(
  key: string,
  value: T,
  ttlSeconds: number = CACHE_TTL.LONG
): Promise<void> {
  if (!redis) return;
  try {
    await redis.set(key, value, { ex: ttlSeconds });
  } catch (error) {
    console.warn(`[Redis Cache Warning] Failed to set key "${key}":`, error);
  }
}

/**
 * Safe Redis Invalidate/Delete Helper
 */
export async function invalidateCache(...keys: string[]): Promise<void> {
  if (!redis || keys.length === 0) return;
  try {
    const validKeys = keys.filter(Boolean);
    if (validKeys.length > 0) {
      await redis.del(...validKeys);
    }
  } catch (error) {
    console.warn(`[Redis Cache Warning] Failed to delete keys:`, keys, error);
  }
}

/**
 * Versioned Namespace Invalidation Pattern (O(1) Purge)
 * Instead of expensive SCAN/KEYS commands, catalog query keys include a namespace version.
 * Calling bumpCacheVersion(namespace) instantly invalidates all cached queries under that namespace.
 */
export async function getCacheVersion(namespace: string): Promise<number> {
  if (!redis) return 1;
  try {
    const ver = await redis.get<number>(`cache:ver:${namespace}`);
    return ver ? Number(ver) : 1;
  } catch {
    return 1;
  }
}

export async function bumpCacheVersion(namespace: string): Promise<number> {
  if (!redis) return 1;
  try {
    const newVer = await redis.incr(`cache:ver:${namespace}`);
    return newVer;
  } catch (error) {
    console.warn(`[Redis Cache Warning] Failed to bump version for namespace "${namespace}":`, error);
    return 1;
  }
}

/**
 * High-Level Cache Wrapper with Fallback
 * Checks Redis first; runs fetcher() on miss and persists result with specified TTL.
 */
export async function getOrSetCache<T>(
  key: string,
  fetcher: () => Promise<T>,
  ttlSeconds: number = CACHE_TTL.LONG
): Promise<T> {
  // 1. Try to fetch from Redis
  const cached = await getCachedJson<T>(key);
  if (cached !== null && cached !== undefined) {
    return cached;
  }

  // 2. Fetch fresh data
  const freshData = await fetcher();

  // 3. Persist in Redis asynchronously (fire & forget to avoid delaying response)
  if (freshData !== null && freshData !== undefined) {
    setCachedJson(key, freshData, ttlSeconds).catch(() => {});
  }

  return freshData;
}

/**
 * Structured Cache Key Generators
 */
export const cacheKeys = {
  geoTaxonomy: () => "taxonomy:nigeria:geo_and_amenities",
  trendingLocations: () => "marketplace:trending_locations",
  catalog: (filters: Record<string, any>, page: number, version: number) => {
    // Generate deterministic hash/string from filter object
    const serializedFilters = Object.entries(filters)
      .filter(([_, v]) => v !== undefined && v !== null && v !== "")
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([k, v]) => `${k}:${v}`)
      .join("|");
    return `marketplace:v${version}:catalog:p${page}:${serializedFilters || "all"}`;
  },
  propertyDetail: (slug: string) => `property:slug:${slug}`,
  propertyById: (id: string) => `property:id:${id}`,
  featuredProperties: () => `marketplace:featured_properties`,
  agentProfile: (idOrSlug: string) => `agent:profile:${idOrSlug.toLowerCase()}`,
  agentProperties: (idOrSlug: string) => `agent:properties:${idOrSlug.toLowerCase()}`,
  agencyDetails: (agencyId: string) => `agency:details:${agencyId}`,
  moderationMetrics: () => `admin:moderation:metrics`,
  userSession: (clerkId: string) => `user:session:v2:${clerkId}`,
  entitlements: (targetId: string, version: number = 1) =>
    version > 1 ? `entitlements:v${version}:${targetId}` : `entitlements:${targetId}`,
  globalListingLimits: () => "system:settings:listing_limits_mode",
  payment: (reference: string) => `payment:ref:${reference}`,
  ratingSummary: (targetType: string, targetId: string) => `rating:summary:${targetType.toLowerCase()}:${targetId.toLowerCase()}`,
  targetReviews: (targetType: string, targetId: string) => `rating:reviews:${targetType.toLowerCase()}:${targetId.toLowerCase()}`,
  agentTrackRecord: (agentIdOrSlug: string) => `agent:track_record:${agentIdOrSlug.toLowerCase()}`,
  buyerPendingDeals: (userId: string) => `buyer:pending_deals:${userId}`,
};

