/**
 * OfflineDbManager
 *
 * CRUD operations for IndexedDB offline storage.
 * Handles large assets (videos > 5MB) and project metadata.
 */

import { offlineDb } from './schema';
import type {
  OfflineAsset,
  OfflineProject,
  DownloadQueueItem,
  ProjectOfflineStatus,
  PreloadJobStatus,
} from '../../types/offline';

export class OfflineDbManager {
  // ============================================
  // ASSETS
  // ============================================

  /**
   * Store an asset blob in IndexedDB
   */
  static async storeAsset(asset: OfflineAsset): Promise<void> {
    await offlineDb.assets.put(asset);
  }

  /**
   * Get an asset by ID
   */
  static async getAsset(id: string): Promise<OfflineAsset | undefined> {
    return offlineDb.assets.get(id);
  }

  /**
   * Get an asset by URL
   */
  static async getAssetByUrl(url: string): Promise<OfflineAsset | undefined> {
    return offlineDb.assets.where('url').equals(url).first();
  }

  /**
   * Get all assets for a project
   */
  static async getProjectAssets(projectId: string): Promise<OfflineAsset[]> {
    return offlineDb.assets.where('projectId').equals(projectId).toArray();
  }

  /**
   * Delete an asset
   */
  static async deleteAsset(id: string): Promise<void> {
    await offlineDb.assets.delete(id);
  }

  /**
   * Delete all assets for a project
   */
  static async deleteProjectAssets(projectId: string): Promise<number> {
    return offlineDb.assets.where('projectId').equals(projectId).delete();
  }

  /**
   * Check if an asset exists
   */
  static async hasAsset(id: string): Promise<boolean> {
    const count = await offlineDb.assets.where('id').equals(id).count();
    return count > 0;
  }

  /**
   * Check if an asset URL exists
   */
  static async hasAssetByUrl(url: string): Promise<boolean> {
    const count = await offlineDb.assets.where('url').equals(url).count();
    return count > 0;
  }

  /**
   * Get total storage used by assets
   */
  static async getTotalAssetsSize(): Promise<number> {
    const assets = await offlineDb.assets.toArray();
    return assets.reduce((total, asset) => total + asset.sizeBytes, 0);
  }

  /**
   * Get storage used by a project's assets
   */
  static async getProjectAssetsSize(projectId: string): Promise<number> {
    const assets = await offlineDb.assets
      .where('projectId')
      .equals(projectId)
      .toArray();
    return assets.reduce((total, asset) => total + asset.sizeBytes, 0);
  }

  // ============================================
  // PROJECTS
  // ============================================

  /**
   * Store or update project offline metadata
   */
  static async upsertProject(project: OfflineProject): Promise<void> {
    await offlineDb.projects.put(project);
  }

  /**
   * Get project by ID
   */
  static async getProject(id: string): Promise<OfflineProject | undefined> {
    return offlineDb.projects.get(id);
  }

  /**
   * Get project by slug
   */
  static async getProjectBySlug(
    slug: string,
  ): Promise<OfflineProject | undefined> {
    return offlineDb.projects.where('slug').equals(slug).first();
  }

  /**
   * Get all offline projects
   */
  static async getAllProjects(): Promise<OfflineProject[]> {
    return offlineDb.projects.toArray();
  }

  /**
   * Update project status
   */
  static async updateProjectStatus(
    id: string,
    status: ProjectOfflineStatus,
  ): Promise<void> {
    await offlineDb.projects.update(id, { status });
  }

  /**
   * Update project download progress
   */
  static async updateProjectProgress(
    id: string,
    downloadedAssets: number,
    downloadedSizeBytes: number,
  ): Promise<void> {
    await offlineDb.projects.update(id, {
      downloadedAssets,
      downloadedSizeBytes,
    });
  }

  /**
   * Delete project and all its assets
   */
  static async deleteProject(id: string): Promise<void> {
    await offlineDb.transaction(
      'rw',
      [offlineDb.projects, offlineDb.assets, offlineDb.downloadQueue],
      async () => {
        await offlineDb.projects.delete(id);
        await offlineDb.assets.where('projectId').equals(id).delete();
        await offlineDb.downloadQueue.where('projectId').equals(id).delete();
      },
    );
  }

  // ============================================
  // DOWNLOAD QUEUE
  // ============================================

  /**
   * Add item to download queue
   */
  static async addToQueue(item: DownloadQueueItem): Promise<void> {
    await offlineDb.downloadQueue.put(item);
  }

  /**
   * Get queue item by ID
   */
  static async getQueueItem(
    id: string,
  ): Promise<DownloadQueueItem | undefined> {
    return offlineDb.downloadQueue.get(id);
  }

  /**
   * Get all queued items for a project
   */
  static async getProjectQueue(
    projectId: string,
  ): Promise<DownloadQueueItem[]> {
    return offlineDb.downloadQueue
      .where('projectId')
      .equals(projectId)
      .sortBy('priority');
  }

  /**
   * Get pending queue items (sorted by priority)
   */
  static async getPendingQueue(): Promise<DownloadQueueItem[]> {
    return offlineDb.downloadQueue
      .where('status')
      .equals('queued')
      .sortBy('priority');
  }

  /**
   * Update queue item status
   */
  static async updateQueueStatus(
    id: string,
    status: PreloadJobStatus,
    error?: string,
  ): Promise<void> {
    const updates: Partial<DownloadQueueItem> = {
      status,
      lastAttemptAt: Date.now(),
    };
    if (error) {
      updates.error = error;
    }
    await offlineDb.downloadQueue.update(id, updates);
  }

  /**
   * Update queue item progress
   */
  static async updateQueueProgress(
    id: string,
    bytesLoaded: number,
    totalBytes: number,
  ): Promise<void> {
    await offlineDb.downloadQueue.update(id, { bytesLoaded, totalBytes });
  }

  /**
   * Increment retry count
   */
  static async incrementRetry(id: string): Promise<number> {
    const item = await offlineDb.downloadQueue.get(id);
    if (!item) return 0;
    const newRetryCount = item.retryCount + 1;
    await offlineDb.downloadQueue.update(id, { retryCount: newRetryCount });
    return newRetryCount;
  }

  /**
   * Remove item from queue
   */
  static async removeFromQueue(id: string): Promise<void> {
    await offlineDb.downloadQueue.delete(id);
  }

  /**
   * Clear project download queue
   */
  static async clearProjectQueue(projectId: string): Promise<number> {
    return offlineDb.downloadQueue
      .where('projectId')
      .equals(projectId)
      .delete();
  }

  /**
   * Clear entire download queue
   */
  static async clearQueue(): Promise<void> {
    await offlineDb.downloadQueue.clear();
  }

  /**
   * Reset failed queue items to queued status
   */
  static async resetFailedItems(projectId?: string): Promise<number> {
    const query = projectId
      ? offlineDb.downloadQueue
          .where('projectId')
          .equals(projectId)
          .filter((item) => item.status === 'error')
      : offlineDb.downloadQueue.filter((item) => item.status === 'error');

    const failedItems = await query.toArray();

    await Promise.all(
      failedItems.map((item) =>
        offlineDb.downloadQueue.update(item.id, {
          status: 'queued',
          retryCount: 0,
          error: undefined,
        }),
      ),
    );

    return failedItems.length;
  }

  // ============================================
  // UTILITY
  // ============================================

  /**
   * Clear all offline data
   */
  static async clearAll(): Promise<void> {
    await offlineDb.transaction(
      'rw',
      [offlineDb.projects, offlineDb.assets, offlineDb.downloadQueue],
      async () => {
        await offlineDb.projects.clear();
        await offlineDb.assets.clear();
        await offlineDb.downloadQueue.clear();
      },
    );
  }

  /**
   * Get database statistics
   */
  static async getStats(): Promise<{
    projectCount: number;
    assetCount: number;
    queueCount: number;
    totalSizeBytes: number;
  }> {
    const [projectCount, assetCount, queueCount, totalSizeBytes] =
      await Promise.all([
        offlineDb.projects.count(),
        offlineDb.assets.count(),
        offlineDb.downloadQueue.count(),
        this.getTotalAssetsSize(),
      ]);

    return { projectCount, assetCount, queueCount, totalSizeBytes };
  }
}
