/**
 * TASKMATE - Offline Module
 * ==========================
 * IndexedDB storage and offline support
 */

import store from './store.js';
import api from './api.js';

const DB_NAME = 'TaskMateDB';
const DB_VERSION = 1;

class OfflineManager {
  constructor() {
    this.db = null;
    this.isReady = false;
    this.pendingSync = [];
  }

  // Initialize IndexedDB
  async init() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(DB_NAME, DB_VERSION);

      request.onerror = () => {
        console.error('[Offline] Failed to open database');
        reject(request.error);
      };

      request.onsuccess = () => {
        this.db = request.result;
        this.isReady = true;
        console.log('[Offline] Database ready');
        resolve();
      };

      request.onupgradeneeded = (event) => {
        const db = event.target.result;

        // Projects store
        if (!db.objectStoreNames.contains('projects')) {
          const projectStore = db.createObjectStore('projects', { keyPath: 'id' });
          projectStore.createIndex('name', 'name', { unique: false });
        }

        // Columns store
        if (!db.objectStoreNames.contains('columns')) {
          const columnStore = db.createObjectStore('columns', { keyPath: 'id' });
          columnStore.createIndex('project_id', 'project_id', { unique: false });
          columnStore.createIndex('position', 'position', { unique: false });
        }

        // Tasks store
        if (!db.objectStoreNames.contains('tasks')) {
          const taskStore = db.createObjectStore('tasks', { keyPath: 'id' });
          taskStore.createIndex('project_id', 'project_id', { unique: false });
          taskStore.createIndex('column_id', 'column_id', { unique: false });
          taskStore.createIndex('assignee_id', 'assignee_id', { unique: false });
          taskStore.createIndex('due_date', 'due_date', { unique: false });
        }

        // Labels store
        if (!db.objectStoreNames.contains('labels')) {
          const labelStore = db.createObjectStore('labels', { keyPath: 'id' });
          labelStore.createIndex('project_id', 'project_id', { unique: false });
        }

        // Pending operations store (for offline sync)
        if (!db.objectStoreNames.contains('pending_operations')) {
          const pendingStore = db.createObjectStore('pending_operations', {
            keyPath: 'id',
            autoIncrement: true
          });
          pendingStore.createIndex('timestamp', 'timestamp', { unique: false });
          pendingStore.createIndex('type', 'type', { unique: false });
        }

        // Cache metadata store
        if (!db.objectStoreNames.contains('cache_meta')) {
          db.createObjectStore('cache_meta', { keyPath: 'key' });
        }

        console.log('[Offline] Database schema created');
      };
    });
  }

  // Generic CRUD operations
  async put(storeName, data) {
    if (!this.db) return;

    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(storeName, 'readwrite');
      const store = transaction.objectStore(storeName);
      const request = store.put(data);

      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  async get(storeName, key) {
    if (!this.db) return null;

    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(storeName, 'readonly');
      const store = transaction.objectStore(storeName);
      const request = store.get(key);

      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  async getAll(storeName, indexName = null, query = null) {
    if (!this.db) return [];

    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(storeName, 'readonly');
      const store = transaction.objectStore(storeName);
      const target = indexName ? store.index(indexName) : store;
      const request = query ? target.getAll(query) : target.getAll();

      request.onsuccess = () => resolve(request.result || []);
      request.onerror = () => reject(request.error);
    });
  }

  async delete(storeName, key) {
    if (!this.db) return;

    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(storeName, 'readwrite');
      const store = transaction.objectStore(storeName);
      const request = store.delete(key);

      request.onsuccess = () => resolve();
      request.onerror = () => reject(request.error);
    });
  }

  async clear(storeName) {
    if (!this.db) return;

    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(storeName, 'readwrite');
      const store = transaction.objectStore(storeName);
      const request = store.clear();

      request.onsuccess = () => resolve();
      request.onerror = () => reject(request.error);
    });
  }

  // =====================
  // DATA CACHING
  // =====================

  async cacheProjects(projects) {
    await this.clear('projects');
    for (const project of projects) {
      await this.put('projects', project);
    }
    await this.setCacheMeta('projects', Date.now());
  }

  async getCachedProjects() {
    return this.getAll('projects');
  }

  async cacheColumns(projectId, columns) {
    // Clear existing columns for project
    const existing = await this.getAll('columns', 'project_id', projectId);
    for (const col of existing) {
      await this.delete('columns', col.id);
    }

    // Store new columns
    for (const column of columns) {
      await this.put('columns', { ...column, project_id: projectId });
    }
    await this.setCacheMeta(`columns_${projectId}`, Date.now());
  }

  async getCachedColumns(projectId) {
    return this.getAll('columns', 'project_id', projectId);
  }

  async cacheTasks(projectId, tasks) {
    // Clear existing tasks for project
    const existing = await this.getAll('tasks', 'project_id', projectId);
    for (const task of existing) {
      await this.delete('tasks', task.id);
    }

    // Store new tasks
    for (const task of tasks) {
      await this.put('tasks', { ...task, project_id: projectId });
    }
    await this.setCacheMeta(`tasks_${projectId}`, Date.now());
  }

  async getCachedTasks(projectId) {
    return this.getAll('tasks', 'project_id', projectId);
  }

  async cacheLabels(projectId, labels) {
    const existing = await this.getAll('labels', 'project_id', projectId);
    for (const label of existing) {
      await this.delete('labels', label.id);
    }

    for (const label of labels) {
      await this.put('labels', { ...label, project_id: projectId });
    }
    await this.setCacheMeta(`labels_${projectId}`, Date.now());
  }

  async getCachedLabels(projectId) {
    return this.getAll('labels', 'project_id', projectId);
  }

  // Cache metadata
  async setCacheMeta(key, timestamp) {
    await this.put('cache_meta', { key, timestamp });
  }

  async getCacheMeta(key) {
    return this.get('cache_meta', key);
  }

  async isCacheValid(key, maxAge = 5 * 60 * 1000) {
    const meta = await this.getCacheMeta(key);
    if (!meta) return false;

    return Date.now() - meta.timestamp < maxAge;
  }

  // =====================
  // OFFLINE OPERATIONS
  // =====================

  async queueOperation(operation) {
    const pendingOp = {
      ...operation,
      timestamp: Date.now(),
      synced: false
    };

    await this.put('pending_operations', pendingOp);
    store.setSyncStatus('offline');

    return pendingOp;
  }

  async getPendingOperations() {
    return this.getAll('pending_operations');
  }

  async markOperationSynced(operationId) {
    await this.delete('pending_operations', operationId);
  }

  async clearPendingOperations() {
    await this.clear('pending_operations');
  }

  // Sync pending operations with server
  async syncPendingOperations() {
    if (!navigator.onLine) {
      return { success: false, reason: 'offline' };
    }

    const operations = await this.getPendingOperations();

    if (operations.length === 0) {
      return { success: true, synced: 0 };
    }

    console.log(`[Offline] Syncing ${operations.length} pending operations`);
    store.setSyncStatus('syncing');

    let syncedCount = 0;
    const errors = [];

    // Sort by timestamp
    operations.sort((a, b) => a.timestamp - b.timestamp);

    for (const op of operations) {
      try {
        await this.executePendingOperation(op);
        await this.markOperationSynced(op.id);
        syncedCount++;
      } catch (error) {
        console.error(`[Offline] Failed to sync operation:`, op, error);
        errors.push({ operation: op, error: error.message });

        // Stop on auth errors
        if (error.status === 401) {
          break;
        }
      }
    }

    const remaining = await this.getPendingOperations();
    store.setSyncStatus(remaining.length > 0 ? 'offline' : 'synced');

    return {
      success: errors.length === 0,
      synced: syncedCount,
      errors
    };
  }

  async executePendingOperation(op) {
    const projectId = op.projectId;

    switch (op.type) {
      case 'task:create':
        const newTask = await api.createTask(projectId, op.data);
        // Update local ID mapping
        if (op.tempId) {
          await this.updateTaskId(op.tempId, newTask.id);
        }
        break;

      case 'task:update':
        await api.updateTask(projectId, op.taskId, op.data);
        break;

      case 'task:delete':
        await api.deleteTask(projectId, op.taskId);
        break;

      case 'task:move':
        await api.moveTask(projectId, op.taskId, op.columnId, op.position);
        break;

      case 'column:create':
        const newColumn = await api.createColumn(projectId, op.data);
        if (op.tempId) {
          await this.updateColumnId(op.tempId, newColumn.id);
        }
        break;

      case 'column:update':
        await api.updateColumn(projectId, op.columnId, op.data);
        break;

      case 'column:delete':
        await api.deleteColumn(projectId, op.columnId);
        break;

      case 'subtask:create':
        await api.createSubtask(projectId, op.taskId, op.data);
        break;

      case 'subtask:update':
        await api.updateSubtask(projectId, op.taskId, op.subtaskId, op.data);
        break;

      case 'comment:create':
        await api.createComment(projectId, op.taskId, op.data);
        break;

      default:
        console.warn(`[Offline] Unknown operation type: ${op.type}`);
    }
  }

  async updateTaskId(tempId, realId) {
    // Update in cache
    const task = await this.get('tasks', tempId);
    if (task) {
      await this.delete('tasks', tempId);
      await this.put('tasks', { ...task, id: realId });
    }

    // Update pending operations that reference this task
    const pending = await this.getPendingOperations();
    for (const op of pending) {
      if (op.taskId === tempId) {
        await this.put('pending_operations', { ...op, taskId: realId });
      }
    }
  }

  async updateColumnId(tempId, realId) {
    const column = await this.get('columns', tempId);
    if (column) {
      await this.delete('columns', tempId);
      await this.put('columns', { ...column, id: realId });
    }

    // Update tasks in this column
    const tasks = await this.getAll('tasks');
    for (const task of tasks) {
      if (task.column_id === tempId) {
        await this.put('tasks', { ...task, column_id: realId });
      }
    }
  }

  // =====================
  // OFFLINE DATA LOADING
  // =====================

  async loadOfflineData() {
    const projectId = store.get('currentProjectId');
    if (!projectId) return;

    console.log('[Offline] Loading cached data');

    try {
      // Load from cache
      const [columns, tasks, labels] = await Promise.all([
        this.getCachedColumns(projectId),
        this.getCachedTasks(projectId),
        this.getCachedLabels(projectId)
      ]);

      store.setColumns(columns);
      store.setTasks(tasks);
      store.setLabels(labels);

      console.log('[Offline] Loaded cached data:', {
        columns: columns.length,
        tasks: tasks.length,
        labels: labels.length
      });
    } catch (error) {
      console.error('[Offline] Failed to load cached data:', error);
    }
  }

  // =====================
  // HELPERS
  // =====================

  hasPendingOperations() {
    return this.getPendingOperations().then(ops => ops.length > 0);
  }

  async getPendingOperationCount() {
    const ops = await this.getPendingOperations();
    return ops.length;
  }

  // Clear all cached data
  async clearAllData() {
    const stores = ['projects', 'columns', 'tasks', 'labels', 'pending_operations', 'cache_meta'];

    for (const storeName of stores) {
      await this.clear(storeName);
    }

    console.log('[Offline] Cleared all cached data');
  }
}

// Create singleton instance
const offlineManager = new OfflineManager();

// Initialize on load
offlineManager.init().catch(console.error);

// Listen for online event to sync
window.addEventListener('online', async () => {
  console.log('[Offline] Back online, starting sync');
  await offlineManager.syncPendingOperations();
});

// Subscribe to store changes to cache data
store.subscribe('columns', async (columns) => {
  const projectId = store.get('currentProjectId');
  if (projectId && columns.length > 0) {
    await offlineManager.cacheColumns(projectId, columns);
  }
});

store.subscribe('tasks', async (tasks) => {
  const projectId = store.get('currentProjectId');
  if (projectId && tasks.length > 0) {
    await offlineManager.cacheTasks(projectId, tasks);
  }
});

store.subscribe('labels', async (labels) => {
  const projectId = store.get('currentProjectId');
  if (projectId && labels.length > 0) {
    await offlineManager.cacheLabels(projectId, labels);
  }
});

store.subscribe('projects', async (projects) => {
  if (projects.length > 0) {
    await offlineManager.cacheProjects(projects);
  }
});

export default offlineManager;
