import AsyncStorage from '@react-native-async-storage/async-storage';

/** Keys used for cache entries */
export const CACHE_KEYS = {
  RACES: 'races_cache',
  TIPS: 'tips_cache',
  race: (id: string): string => `race_${id}_cache`,
} as const;

/** Default TTL values in seconds */
export const DEFAULT_TTL = {
  RACES: 300,       // 5 minutes
  TIPS: 600,        // 10 minutes
  SINGLE_RACE: 120, // 2 minutes
} as const;

interface CacheEntry<T = any> {
  data: T;
  cachedAt: string; // ISO 8601
  ttlSeconds: number;
}

/**
 * Read a cached value. Returns `null` when the entry is missing or expired.
 */
export async function getCachedData<T = any>(
  key: string,
  ttlSeconds: number,
): Promise<T | null> {
  try {
    const raw = await AsyncStorage.getItem(key);
    if (!raw) return null;

    const entry: CacheEntry<T> = JSON.parse(raw);

    if (!isCacheEntryValid(entry, ttlSeconds)) {
      await AsyncStorage.removeItem(key);
      return null;
    }

    return entry.data;
  } catch {
    return null;
  }
}

/**
 * Store a value in the cache.
 */
export async function setCachedData<T = any>(
  key: string,
  data: T,
  ttlSeconds: number,
): Promise<void> {
  try {
    const entry: CacheEntry<T> = {
      data,
      cachedAt: new Date().toISOString(),
      ttlSeconds,
    };
    await AsyncStorage.setItem(key, JSON.stringify(entry));
  } catch {
    // Silently fail — cache is best-effort
  }
}

/**
 * Remove a single cache entry.
 */
export async function invalidateCache(key: string): Promise<void> {
  try {
    await AsyncStorage.removeItem(key);
  } catch {
    // Silently fail
  }
}

/**
 * Remove ALL cache entries that match our known patterns.
 * This avoids wiping unrelated AsyncStorage keys (e.g. auth tokens).
 */
export async function invalidateAllCache(): Promise<void> {
  try {
    const allKeys = await AsyncStorage.getAllKeys();
    const cacheKeys = allKeys.filter(
      (k) =>
        k === CACHE_KEYS.RACES ||
        k === CACHE_KEYS.TIPS ||
        k.startsWith('race_'),
    );
    if (cacheKeys.length > 0) {
      await AsyncStorage.multiRemove(cacheKeys);
    }
  } catch {
    // Silently fail
  }
}

/**
 * Check whether a given cache key still holds valid (non-expired) data.
 */
export async function isCacheValid(
  key: string,
  ttlSeconds: number,
): Promise<boolean> {
  try {
    const raw = await AsyncStorage.getItem(key);
    if (!raw) return false;

    const entry: CacheEntry = JSON.parse(raw);
    return isCacheEntryValid(entry, ttlSeconds);
  } catch {
    return false;
  }
}

// ── Helpers ────────────────────────────────────────────────

function isCacheEntryValid(entry: CacheEntry, ttlSeconds: number): boolean {
  const cachedAt = new Date(entry.cachedAt).getTime();
  if (isNaN(cachedAt)) return false;

  const ageSeconds = (Date.now() - cachedAt) / 1000;
  // Use the smaller of stored TTL and requested TTL so callers can tighten
  // expiry without having to re-write every cache entry.
  const effectiveTtl = Math.min(entry.ttlSeconds ?? ttlSeconds, ttlSeconds);
  return ageSeconds < effectiveTtl;
}

// ── Convenience wrappers for common cache operations ──────

export function getCachedRaces(): Promise<any | null> {
  return getCachedData(CACHE_KEYS.RACES, DEFAULT_TTL.RACES);
}

export function setCachedRaces(data: any): Promise<void> {
  return setCachedData(CACHE_KEYS.RACES, data, DEFAULT_TTL.RACES);
}

export function getCachedTips(): Promise<any | null> {
  return getCachedData(CACHE_KEYS.TIPS, DEFAULT_TTL.TIPS);
}

export function setCachedTips(data: any): Promise<void> {
  return setCachedData(CACHE_KEYS.TIPS, data, DEFAULT_TTL.TIPS);
}

export function getCachedRace(raceId: string): Promise<any | null> {
  return getCachedData(CACHE_KEYS.race(raceId), DEFAULT_TTL.SINGLE_RACE);
}

export function setCachedRace(raceId: string, data: any): Promise<void> {
  return setCachedData(CACHE_KEYS.race(raceId), data, DEFAULT_TTL.SINGLE_RACE);
}
