import { openDB, type DBSchema, type IDBPDatabase } from 'idb'

interface CacheSchema extends DBSchema {
  cache: {
    key: string
    value: {
      data: unknown
      expiresAt: number
      createdAt: number
    }
  }
}

interface CacheItem<T = unknown> {
  data: T
  expiresAt: number
  createdAt: number
}

export enum TTL {
  OneMinute = 60000,
  FiveMinutes = 300000,
  OneHour = 3600000,
  OneDay = 86400000,
  OneWeek = 604800000,
  OneMonth = 2592000000,
}

export class Cache {
  private db: IDBPDatabase<CacheSchema> | null = null
  private readonly dbName: string
  private readonly dbVersion: number

  constructor() {
    this.dbName = 'app-cache'
    this.dbVersion = 1
  }

  /**
   * Initialize the database connection
   */
  private async database(): Promise<IDBPDatabase<CacheSchema>> {
    if (this.db) {
      return this.db
    }

    try {
      this.db = await openDB<CacheSchema>(this.dbName, this.dbVersion, {
        upgrade(db) {
          if (!db.objectStoreNames.contains('cache')) {
            db.createObjectStore('cache')
          }
        },
      })
      return this.db
    } catch {
      throw new Error('Cache initialization failed')
    }
  }

  /**
   * Store data in the cache with optional TTL
   *
   * @param key - The key to store the data under
   * @param data - The data to store
   * @param ttl - Time to live in milliseconds
   * @returns A promise that resolves to true if the data was stored successfully, false otherwise
   */
  async set<T>(key: string, data: T, ttl?: number): Promise<boolean> {
    try {
      const db = await this.database()
      const now = Date.now()
      const expiresAt = ttl ? now + ttl : Number.MAX_SAFE_INTEGER

      const item: CacheItem<T> = {
        data,
        expiresAt,
        createdAt: now,
      }

      await db.put('cache', item, key)
      return true
    } catch {
      return false
    }
  }

  /**
   * Retrieve data from the cache with type safety
   */
  async get<T = unknown>(key: string): Promise<T | null> {
    try {
      const db = await this.database()
      const item = await db.get('cache', key)

      if (!item) {
        return null
      }

      const now = Date.now()

      // Check if item has expired
      if (now > item.expiresAt) {
        // Clean up expired item
        await this.delete(key)
        return null
      }

      return item.data as T
    } catch {
      return null
    }
  }

  /**
   * Check if a key exists and is not expired
   */
  async has(key: string): Promise<boolean> {
    try {
      const db = await this.database()
      const item = await db.get('cache', key)

      if (!item) {
        return false
      }

      const now = Date.now()

      if (now > item.expiresAt) {
        // Clean up expired item
        await this.delete(key)
        return false
      }

      return true
    } catch {
      return false
    }
  }

  /**
   * Delete a specific cache item
   */
  async delete(key: string): Promise<boolean> {
    try {
      const db = await this.database()
      await db.delete('cache', key)
      return true
    } catch {
      return false
    }
  }

  /**
   * Clear all cache items
   */
  async clear(): Promise<boolean> {
    try {
      const db = await this.database()
      await db.clear('cache')
      return true
    } catch {
      return false
    }
  }

  /**
   * Get cache statistics
   */
  async stats(): Promise<{
    totalItems: number
    expiredItems: number
    validItems: number
  } | null> {
    try {
      const db = await this.database()
      const allKeys = await db.getAllKeys('cache')
      const now = Date.now()

      let expiredItems = 0
      let validItems = 0

      for (const key of allKeys) {
        const item = await db.get('cache', key)
        if (item) {
          if (now > item.expiresAt) {
            expiredItems++
          } else {
            validItems++
          }
        }
      }

      return {
        totalItems: allKeys.length,
        expiredItems,
        validItems,
      }
    } catch {
      return null
    }
  }

  /**
   * Clean up expired items
   */
  async cleanup(): Promise<number> {
    try {
      const db = await this.database()
      const allKeys = await db.getAllKeys('cache')
      const now = Date.now()
      let cleanedCount = 0

      for (const key of allKeys) {
        const item = await db.get('cache', key)
        if (item && now > item.expiresAt) {
          await db.delete('cache', key)
          cleanedCount++
        }
      }

      return cleanedCount
    } catch {
      return 0
    }
  }

  /**
   * Get multiple cache items at once
   */
  async getMany<T = unknown>(keys: string[]): Promise<(T | null)[]> {
    const results: (T | null)[] = []

    for (const key of keys) {
      const value = await this.get<T>(key)
      results.push(value)
    }

    return results
  }

  /**
   * Set multiple cache items at once
   */
  async setMany<T>(items: { key: string; data: T; ttl?: number }[]): Promise<boolean[]> {
    const results: boolean[] = []

    for (const item of items) {
      const success = await this.set(item.key, item.data, item.ttl)
      results.push(success)
    }

    return results
  }

  /**
   * Close the database connection
   */
  async close(): Promise<void> {
    if (this.db) {
      this.db.close()
      this.db = null
    }
  }
}
