import { Note } from '../types';
import { NextcloudAPI } from '../api/nextcloud';
import { localDB } from '../db/localDB';

export type SyncStatus = 'idle' | 'syncing' | 'error' | 'offline';

export class SyncManager {
  private api: NextcloudAPI | null = null;
  private isOnline: boolean = navigator.onLine;
  private syncInProgress: boolean = false;
  private statusCallback: ((status: SyncStatus, pendingCount: number) => void) | null = null;
  private syncCompleteCallback: (() => void) | null = null;
  private recentlyModifiedNotes: Set<number | string> = new Set();
  private readonly PROTECTION_WINDOW_MS = 10000;

  constructor() {
    window.addEventListener('online', () => {
      this.isOnline = true;
      this.notifyStatus('idle', 0);
    });

    window.addEventListener('offline', () => {
      this.isOnline = false;
      this.notifyStatus('offline', 0);
    });
  }

  setAPI(api: NextcloudAPI | null) {
    this.api = api;
  }

  setStatusCallback(callback: (status: SyncStatus, pendingCount: number) => void) {
    this.statusCallback = callback;
  }

  setSyncCompleteCallback(callback: () => void) {
    this.syncCompleteCallback = callback;
  }

  private notifyStatus(status: SyncStatus, pendingCount: number) {
    if (this.statusCallback) {
      this.statusCallback(status, pendingCount);
    }
  }

  // Load notes: cache-first, then sync in background
  async loadNotes(): Promise<Note[]> {
    // Try to load from cache first (instant)
    const cachedNotes = await localDB.getAllNotes();
    
    // If we have cached notes and we're offline, return them
    if (!this.isOnline) {
      this.notifyStatus('offline', 0);
      return cachedNotes;
    }

    // If we have cached notes, return them immediately
    // Then sync in background
    if (cachedNotes.length > 0) {
      this.syncInBackground();
      return cachedNotes;
    }

    // No cache - must fetch from server
    if (!this.api) {
      throw new Error('API not initialized');
    }

    try {
      this.notifyStatus('syncing', 0);
      await this.fetchAndCacheNotes();
      await this.syncFavoriteStatus();
      const notes = await localDB.getAllNotes();
      this.notifyStatus('idle', 0);
      return notes;
    } catch (error) {
      this.notifyStatus('error', 0);
      throw error;
    }
  }

  // Background sync: compare etags and only fetch changed content
  private async syncInBackground(): Promise<void> {
    if (!this.api || this.syncInProgress) return;

    this.syncInProgress = true;
    try {
      this.notifyStatus('syncing', 0);
      
      // Get metadata for all notes (fast - no content)
      const serverNotes = await this.api.fetchNotesWebDAV();
      const cachedNotes = await localDB.getAllNotes();
      
      // Build maps for comparison
      const serverMap = new Map(serverNotes.map(n => [n.id, n]));
      const cachedMap = new Map(cachedNotes.map(n => [n.id, n]));
      
      // Find notes that need content fetched (new or changed etag)
      const notesToFetch: Note[] = [];
      for (const serverNote of serverNotes) {
        const cached = cachedMap.get(serverNote.id);
        if (!cached || cached.etag !== serverNote.etag) {
          notesToFetch.push(serverNote);
        }
      }
      
      // Fetch content for changed notes
      for (const note of notesToFetch) {
        try {
          const fullNote = await this.api.fetchNoteContentWebDAV(note);
          await localDB.saveNote(fullNote);
        } catch (error) {
          console.error(`Failed to fetch note ${note.id}:`, error);
        }
      }
      
      // Remove deleted notes from cache (but protect recently modified notes)
      for (const cachedNote of cachedNotes) {
        if (!serverMap.has(cachedNote.id)) {
          // Don't delete notes that were recently created/updated (race condition protection)
          if (!this.recentlyModifiedNotes.has(cachedNote.id)) {
            await localDB.deleteNote(cachedNote.id);
          }
        }
      }
      
      // Sync favorite status from API
      await this.syncFavoriteStatus();
      
      this.notifyStatus('idle', 0);
      
      // Notify that sync is complete so UI can reload
      if (this.syncCompleteCallback) {
        this.syncCompleteCallback();
      }
    } catch (error) {
      console.error('Background sync failed:', error);
      this.notifyStatus('error', 0);
    } finally {
      this.syncInProgress = false;
    }
  }

  // Sync favorite status from API to local cache
  private async syncFavoriteStatus(): Promise<void> {
    if (!this.api) return;
    
    try {
      console.log('Syncing favorite status from API...');
      const apiMetadata = await this.api.fetchNotesMetadata();
      const cachedNotes = await localDB.getAllNotes();
      
      // Map API notes by modified timestamp + category for reliable matching
      // (titles can differ between API and WebDAV)
      const apiByTimestamp = new Map<string, {id: number, title: string, favorite: boolean}>();
      const apiByTitle = new Map<string, {id: number, title: string, favorite: boolean}>();
      
      for (const apiNote of apiMetadata) {
        const timestampKey = `${apiNote.modified}:${apiNote.category}`;
        const titleKey = `${apiNote.category}/${apiNote.title}`;
        const noteData = { id: apiNote.id, title: apiNote.title, favorite: apiNote.favorite };
        apiByTimestamp.set(timestampKey, noteData);
        apiByTitle.set(titleKey, noteData);
      }
      
      // Update favorite status in cache for matching notes
      for (const cachedNote of cachedNotes) {
        // Try timestamp match first (most reliable)
        const timestampKey = `${cachedNote.modified}:${cachedNote.category}`;
        let apiData = apiByTimestamp.get(timestampKey);
        
        // Fallback to title match if timestamp doesn't work
        if (!apiData) {
          const titleKey = `${cachedNote.category}/${cachedNote.title}`;
          apiData = apiByTitle.get(titleKey);
        }
        
        if (apiData && cachedNote.favorite !== apiData.favorite) {
          console.log(`Updating favorite status for "${cachedNote.title}": ${cachedNote.favorite} -> ${apiData.favorite}`);
          cachedNote.favorite = apiData.favorite;
          await localDB.saveNote(cachedNote);
        }
      }
      
      console.log('Favorite status sync complete');
    } catch (error) {
      console.error('Failed to sync favorite status:', error);
      // Don't throw - favorite sync is non-critical
    }
  }

  // Fetch all notes and cache them
  private async fetchAndCacheNotes(): Promise<Note[]> {
    if (!this.api) throw new Error('API not initialized');
    
    const serverNotes = await this.api.fetchNotesWebDAV();
    const notesWithContent: Note[] = [];
    
    for (const note of serverNotes) {
      try {
        const fullNote = await this.api.fetchNoteContentWebDAV(note);
        notesWithContent.push(fullNote);
        await localDB.saveNote(fullNote);
      } catch (error) {
        console.error(`Failed to fetch note ${note.id}:`, error);
      }
    }
    
    return notesWithContent;
  }

  // Fetch content for a specific note on-demand
  async fetchNoteContent(note: Note): Promise<Note> {
    if (!this.api) {
      throw new Error('API not initialized');
    }

    if (!this.isOnline) {
      throw new Error('Cannot fetch note content while offline');
    }

    try {
      const fullNote = await this.api.fetchNoteContentWebDAV(note);
      await localDB.saveNote(fullNote);
      return fullNote;
    } catch (error) {
      throw error;
    }
  }

  // Create note on server and cache
  async createNote(title: string, content: string, category: string): Promise<Note> {
    if (!this.api) {
      throw new Error('API not initialized');
    }

    if (!this.isOnline) {
      this.notifyStatus('offline', 0);
      throw new Error('Cannot create note while offline');
    }

    try {
      this.notifyStatus('syncing', 0);
      const note = await this.api.createNoteWebDAV(title, content, category);
      await localDB.saveNote(note);
      
      // Protect this note from being deleted by background sync for a short window
      this.protectNote(note.id);
      
      this.notifyStatus('idle', 0);
      
      // Trigger background sync to fetch any other changes
      this.syncInBackground().catch(err => console.error('Background sync failed:', err));
      
      return note;
    } catch (error) {
      this.notifyStatus('error', 0);
      throw error;
    }
  }

  // Update favorite status via API
  async updateFavoriteStatus(note: Note, favorite: boolean): Promise<void> {
    if (!this.api) {
      throw new Error('API not initialized');
    }

    if (!this.isOnline) {
      // Update locally, will sync when back online
      note.favorite = favorite;
      await localDB.saveNote(note);
      return;
    }

    try {
      // Find API ID for this note
      const apiId = await this.api.findApiIdForNote(note.title, note.category, note.modified);
      
      if (apiId) {
        // Update via API
        await this.api.updateFavoriteStatus(apiId, favorite);
        console.log(`Updated favorite status for "${note.title}" (API ID: ${apiId})`);
      } else {
        console.warn(`Could not find API ID for note: "${note.title}"`);
      }
      
      // Update local cache
      note.favorite = favorite;
      await localDB.saveNote(note);
    } catch (error) {
      console.error('Failed to update favorite status:', error);
      // Still update locally
      note.favorite = favorite;
      await localDB.saveNote(note);
    }
  }

  // Update note on server and cache
  async updateNote(note: Note): Promise<Note> {
    if (!this.api) {
      throw new Error('API not initialized');
    }

    if (!this.isOnline) {
      this.notifyStatus('offline', 0);
      throw new Error('Cannot update note while offline');
    }

    try {
      this.notifyStatus('syncing', 0);
      const oldId = note.id;
      const updatedNote = await this.api.updateNoteWebDAV(note);
      
      // If the note ID changed (due to filename change), delete the old cache entry
      if (oldId !== updatedNote.id) {
        await localDB.deleteNote(oldId);
      }
      
      await localDB.saveNote(updatedNote);
      
      // Protect this note from being deleted by background sync for a short window
      this.protectNote(updatedNote.id);
      
      this.notifyStatus('idle', 0);
      
      // Trigger background sync to fetch any other changes
      this.syncInBackground().catch(err => console.error('Background sync failed:', err));
      
      return updatedNote;
    } catch (error) {
      this.notifyStatus('error', 0);
      throw error;
    }
  }

  // Delete note from server and cache
  async deleteNote(note: Note): Promise<void> {
    if (!this.api) {
      throw new Error('API not initialized');
    }

    if (!this.isOnline) {
      this.notifyStatus('offline', 0);
      throw new Error('Cannot delete note while offline');
    }

    try {
      this.notifyStatus('syncing', 0);
      await this.api.deleteNoteWebDAV(note);
      await localDB.deleteNote(note.id);
      this.notifyStatus('idle', 0);
    } catch (error) {
      this.notifyStatus('error', 0);
      throw error;
    }
  }

  // Move note to different category on server and cache
  async moveNote(note: Note, newCategory: string): Promise<Note> {
    if (!this.api) {
      throw new Error('API not initialized');
    }

    if (!this.isOnline) {
      this.notifyStatus('offline', 0);
      throw new Error('Cannot move note while offline');
    }

    try {
      this.notifyStatus('syncing', 0);
      const movedNote = await this.api.moveNoteWebDAV(note, newCategory);
      await localDB.deleteNote(note.id);
      await localDB.saveNote(movedNote);
      
      // Protect the moved note from being deleted by background sync
      this.protectNote(movedNote.id);
      
      this.notifyStatus('idle', 0);
      
      // Trigger background sync to fetch any other changes
      this.syncInBackground().catch(err => console.error('Background sync failed:', err));
      
      return movedNote;
    } catch (error) {
      this.notifyStatus('error', 0);
      throw error;
    }
  }

  // Manual sync with server
  async syncWithServer(): Promise<void> {
    if (!this.api || !this.isOnline || this.syncInProgress) return;
    await this.syncInBackground();
  }

  getOnlineStatus(): boolean {
    return this.isOnline;
  }

  // Protect a note from being deleted during background sync for a short window
  private protectNote(noteId: number | string): void {
    this.recentlyModifiedNotes.add(noteId);
    setTimeout(() => {
      this.recentlyModifiedNotes.delete(noteId);
    }, this.PROTECTION_WINDOW_MS);
  }
}

export const syncManager = new SyncManager();
