import { dbManager } from '../utils/indexedDB';
import axiosClient from '../api/axiosClient';
import { base64ToBlob } from '../utils/offlineManager';
import { Note, NoteImage, NoteFile } from '../types/note';
import { store } from '../store/index';
import {
  setSyncStatus,
  addNotification,
} from '../store/slices/uiSlice';
import {
  updateNote,
  setPendingSyncCount,
} from '../store/slices/notesSlice';
import { SyncQueueItem } from '../types/note';

const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 5000;

class SyncService {
  private isSyncing = false;
  private syncTimer: ReturnType<typeof setTimeout> | null = null;
  private listeners: Array<() => void> = [];

  /**
   * Запустить синхронизацию
   */
  async startSync(): Promise<void> {
    if (this.isSyncing) {
      console.log('[SyncService] Already syncing, skipping...');
      return;
    }

    const isOnline = navigator.onLine;
    if (!isOnline) {
      console.log('[SyncService] Offline, skipping sync');
      return;
    }

    console.log('[SyncService] Starting sync...');
    this.isSyncing = true;
    store.dispatch(setSyncStatus('syncing'));

    try {
      const queue = await dbManager.getSyncQueue();
      console.log(`[SyncService] Found ${queue.length} items in queue`);

      if (queue.length === 0) {
        console.log('[SyncService] Queue is empty, sync complete');
        this.isSyncing = false;
        store.dispatch(setSyncStatus('idle'));
        this.notifyListeners();
        return;
      }

      let successCount = 0;
      let errorCount = 0;
      
      // Map для отслеживания преобразования temp ID -> server ID
      const idMapping = new Map<string | number, number>();

      // Сортируем по timestamp для обработки в порядке создания
      const sortedQueue = [...queue].sort((a, b) => a.timestamp - b.timestamp);

      for (const item of sortedQueue) {
        try {
          // Обновляем noteId если есть mapping
          let actualNoteId = item.noteId;
          if (idMapping.has(item.noteId)) {
            actualNoteId = idMapping.get(item.noteId)!;
            console.log(`[SyncService] Mapped temp ID ${item.noteId} to server ID ${actualNoteId}`);
          }
          
          const itemWithUpdatedId = { ...item, noteId: actualNoteId };
          await this.processSyncItem(itemWithUpdatedId, idMapping);
          await dbManager.removeFromSyncQueue(item.id);
          successCount++;

          // Обновляем счетчик
          await this.updatePendingCount();
        } catch (error) {
          console.error('[SyncService] Error processing item:', item, error);
          errorCount++;

          // Увеличиваем счетчик попыток - используем оригинальный item (без обновленного ID)
          const updatedItem = {
            ...item,
            retries: item.retries + 1,
            lastError: error instanceof Error ? error.message : 'Unknown error',
          };

          if (updatedItem.retries < MAX_RETRIES) {
            // Сохраняем для повторной попытки
            await dbManager.updateSyncQueueItem(item.id, updatedItem);
            
            // Запускаем отложенную синхронизацию
            this.scheduleRetry();
          } else {
            // Превысили максимум попыток - удаляем
            console.error('[SyncService] Max retries exceeded, removing item:', item);
            await dbManager.removeFromSyncQueue(item.id);
            
            // Обновляем статус заметки на error - пытаемся найти по обоим ID
            if (item.type === 'create' || item.type === 'update') {
              let note = await dbManager.getNote(item.noteId);
              if (!note && idMapping.has(item.noteId)) {
                const serverId = idMapping.get(item.noteId);
                if (serverId) {
                  note = await dbManager.getNote(serverId);
                }
              }
              if (note) {
                store.dispatch(
                  updateNote({
                    ...note,
                    syncStatus: 'error',
                  })
                );
              }
            }

            store.dispatch(
              addNotification({
                message: 'Не удалось синхронизировать некоторые заметки',
                type: 'error',
              })
            );
          }
        }

        // Небольшая задержка между обработкой элементов
        await new Promise((resolve) => setTimeout(resolve, 100));
      }

      console.log(
        `[SyncService] Sync complete. Success: ${successCount}, Errors: ${errorCount}`
      );

      // Полная синхронизация данных после успешной синхронизации очереди
      if (successCount > 0) {
        // Полная синхронизация: загружаем все данные с сервера
        try {
          console.log('[SyncService] Performing full data sync...');
          const { data } = await axiosClient.get<Note[]>("/notes");
          const notesWithSyncStatus = data.map((note) => ({
            ...note,
            syncStatus: 'synced' as const,
          }));
          
          // Получаем текущие заметки из IndexedDB
          const userId = store.getState().auth.userId;
          const localNotes = userId
            ? await dbManager.getNotesByUserId(userId)
            : await dbManager.getAllNotes();
          
          // Создаем Set серверных ID для быстрой проверки
          const serverNoteIds = new Set(notesWithSyncStatus.map(n => n.id));
          
          // Удаляем заметки, которых нет на сервере (кроме временных)
          for (const localNote of localNotes) {
            if (typeof localNote.id === 'string' && localNote.id.startsWith('temp-')) {
              // Временные заметки оставляем (они либо синхронизированы, либо в очереди)
              continue;
            }
            
            if (!serverNoteIds.has(localNote.id as number)) {
              // Заметка удалена на сервере - удаляем из IndexedDB
              console.log(`[SyncService] Removing deleted note from IndexedDB: ${localNote.id}`);
              await dbManager.deleteNote(localNote.id);
            }
          }
          
          // Обновляем/добавляем все актуальные заметки
          await dbManager.saveNotes(notesWithSyncStatus);
          console.log('[SyncService] Full data sync completed');
        } catch (error) {
          console.error('[SyncService] Error during full data sync:', error);
          // Не критично - очередь уже синхронизирована
        }

        store.dispatch(
          addNotification({
            message: `Синхронизировано заметок: ${successCount}`,
            type: 'success',
          })
        );
      }

      if (errorCount > 0 && errorCount === sortedQueue.length) {
        store.dispatch(setSyncStatus('error'));
      } else {
        store.dispatch(setSyncStatus('idle'));
      }

      await this.updatePendingCount();
      this.notifyListeners();
    } catch (error) {
      console.error('[SyncService] Fatal sync error:', error);
      store.dispatch(setSyncStatus('error'));
      store.dispatch(
        addNotification({
          message: 'Ошибка синхронизации',
          type: 'error',
        })
      );
    } finally {
      this.isSyncing = false;
    }
  }

  /**
   * Обработка одного элемента синхронизации
   */
  private async processSyncItem(item: SyncQueueItem, idMapping?: Map<string | number, number>): Promise<void> {
    console.log(`[SyncService] Processing ${item.type} for note ${item.noteId}`);

    switch (item.type) {
      case 'create':
        await this.syncCreate(item, idMapping);
        break;
      case 'update':
        await this.syncUpdate(item, idMapping);
        break;
      case 'delete':
        await this.syncDelete(item);
        break;
      case 'uploadImage':
        await this.syncUploadImage(item);
        break;
      case 'uploadFile':
        await this.syncUploadFile(item);
        break;
      default:
        console.warn(`[SyncService] Unknown sync type: ${item.type}`);
    }
  }

  /**
   * Синхронизация создания заметки
   */
  private async syncCreate(item: SyncQueueItem, idMapping?: Map<string | number, number>): Promise<void> {
    const note = await dbManager.getNote(item.noteId);
    if (!note) {
      throw new Error('Note not found in local database');
    }

    // Создаем заметку на сервере
    const { data: serverNote } = await axiosClient.post('/notes', {
      content: note.content,
      date: note.date,
      time: note.time,
      is_private: note.is_private !== undefined ? note.is_private : 1,
    });

    // Сохраняем маппинг temp ID -> server ID
    if (idMapping && typeof item.noteId === 'string') {
      idMapping.set(item.noteId, serverNote.id);
    }

    // Заменяем временные ID на серверные в локальных данных
    const updatedNote: Note = {
      ...serverNote,
      images: await this.updateImageReferences(note, serverNote),
      files: await this.updateFileReferences(note, serverNote),
      syncStatus: 'synced',
    };

    // Удаляем старую заметку с временным ID
    await dbManager.deleteNote(item.noteId);

    // Сохраняем с серверным ID
    await dbManager.saveNote(updatedNote);

    // Обновляем UI
    store.dispatch(updateNote(updatedNote));

    // Загружаем изображения и файлы если есть
    await this.syncAttachments(note, serverNote.id);
  }

  /**
   * Синхронизация обновления заметки
   */
  private async syncUpdate(item: SyncQueueItem, idMapping?: Map<string | number, number>): Promise<void> {
    // Проверяем сначала, является ли это временной заметкой
    if (typeof item.noteId === 'string' && item.noteId.startsWith('temp-') && !idMapping?.has(item.noteId)) {
      // Если это временная заметка, нужно создать её на сервере
      await this.syncCreate(item, idMapping);
      return;
    }

    const note = await dbManager.getNote(item.noteId);
    if (!note) {
      console.warn(`[SyncService] Note ${item.noteId} not found, skipping update`);
      return;
    }

    // Определяем, какой эндпоинт использовать в зависимости от изменений
    if (item.data.hasOwnProperty('is_pinned')) {
      // Специальный эндпоинт для закрепления
      await axiosClient.put(`/notes/${item.noteId}/pin`);
    } else if (item.data.hasOwnProperty('is_archived')) {
      // Специальный эндпоинт для архивирования
      if (item.data.is_archived === 1) {
        await axiosClient.put(`/notes/${item.noteId}/archive`);
      } else {
        await axiosClient.put(`/notes/${item.noteId}/unarchive`);
      }
    } else {
      // Обычное обновление содержимого
      await axiosClient.put(`/notes/${item.noteId}`, {
        content: note.content,
        skipTimestamp: item.data.skipTimestamp,
        is_private: item.data.is_private !== undefined ? item.data.is_private : note.is_private,
      });
    }

    // Обновляем локальную заметку с новым статусом синхронизации
    const updatedNote: Note = {
      ...note,
      syncStatus: 'synced',
    };

    await dbManager.saveNote(updatedNote);
    store.dispatch(updateNote(updatedNote));
  }

  /**
   * Синхронизация удаления заметки
   */
  private async syncDelete(item: SyncQueueItem): Promise<void> {
    if (typeof item.noteId === 'string' && item.noteId.startsWith('temp-')) {
      // Временная заметка - просто удаляем локально
      await dbManager.deleteNote(item.noteId);
      return;
    }

    // Удаляем на сервере
    await axiosClient.delete(`/notes/${item.noteId}`);
    await dbManager.deleteNote(item.noteId);
  }

  /**
   * Синхронизация загрузки изображения
   */
  private async syncUploadImage(item: SyncQueueItem): Promise<void> {
    const note = await dbManager.getNote(item.noteId);
    if (!note) {
      throw new Error('Note not found');
    }

    const tempImageId = item.data.imageId;
    const tempImage = note.images.find((img) => img.id === tempImageId);

    if (!tempImage || !tempImage.base64Data) {
      console.warn('[SyncService] Temp image not found or no base64 data');
      return;
    }

    // Конвертируем base64 в blob
    const blob = base64ToBlob(tempImage.base64Data, tempImage.mime_type);
    const file = new File([blob], tempImage.original_name, {
      type: tempImage.mime_type,
    });

    // Загружаем на сервер
    const formData = new FormData();
    formData.append('images', file);

    const { data: uploadedImages } = await axiosClient.post(
      `/notes/${item.noteId}/images`,
      formData,
      {
        headers: { 'Content-Type': 'multipart/form-data' },
      }
    );

    // Обновляем заметку с серверными данными
    const updatedImages = note.images.map((img) =>
      img.id === tempImageId ? uploadedImages[0] : img
    );

    const updatedNote: Note = {
      ...note,
      images: updatedImages,
      syncStatus: 'synced',
    };

    await dbManager.saveNote(updatedNote);
    store.dispatch(updateNote(updatedNote));
  }

  /**
   * Синхронизация загрузки файла
   */
  private async syncUploadFile(item: SyncQueueItem): Promise<void> {
    const note = await dbManager.getNote(item.noteId);
    if (!note) {
      throw new Error('Note not found');
    }

    const tempFileId = item.data.fileId;
    const tempFile = note.files.find((file) => file.id === tempFileId);

    if (!tempFile || !tempFile.base64Data) {
      console.warn('[SyncService] Temp file not found or no base64 data');
      return;
    }

    // Конвертируем base64 в blob
    const blob = base64ToBlob(tempFile.base64Data, tempFile.mime_type);
    const file = new File([blob], tempFile.original_name, {
      type: tempFile.mime_type,
    });

    // Загружаем на сервер
    const formData = new FormData();
    formData.append('files', file);

    const { data: uploadedFiles } = await axiosClient.post(
      `/notes/${item.noteId}/files`,
      formData,
      {
        headers: { 'Content-Type': 'multipart/form-data' },
      }
    );

    // Обновляем заметку с серверными данными
    const updatedFiles = note.files.map((file) =>
      file.id === tempFileId ? uploadedFiles[0] : file
    );

    const updatedNote: Note = {
      ...note,
      files: updatedFiles,
      syncStatus: 'synced',
    };

    await dbManager.saveNote(updatedNote);
    store.dispatch(updateNote(updatedNote));
  }

  /**
   * Обновление ссылок на изображения после создания заметки на сервере
   */
  private async updateImageReferences(
    localNote: Note,
    _serverNote: Note
  ): Promise<NoteImage[]> {
    // Если нет изображений с base64, возвращаем как есть
    const hasBase64Images = localNote.images.some((img) => img.base64Data);
    if (!hasBase64Images) {
      return localNote.images;
    }

    // Возвращаем изображения с временными ID (будут обновлены при загрузке)
    return localNote.images;
  }

  /**
   * Обновление ссылок на файлы после создания заметки на сервере
   */
  private async updateFileReferences(
    localNote: Note,
    _serverNote: Note
  ): Promise<NoteFile[]> {
    // Если нет файлов с base64, возвращаем как есть
    const hasBase64Files = localNote.files.some((file) => file.base64Data);
    if (!hasBase64Files) {
      return localNote.files;
    }

    // Возвращаем файлы с временными ID (будут обновлены при загрузке)
    return localNote.files;
  }

  /**
   * Синхронизация вложений (изображения и файлы)
   */
  private async syncAttachments(localNote: Note, serverNoteId: number): Promise<void> {
    const hasAttachments =
      localNote.images.some((img) => img.base64Data) ||
      localNote.files.some((file) => file.base64Data);

    if (!hasAttachments) {
      return;
    }

    // Создаем задачи синхронизации для вложений
    for (const image of localNote.images) {
      if (image.base64Data) {
        await dbManager.addToSyncQueue({
          type: 'uploadImage',
          noteId: serverNoteId,
          data: { imageId: image.id },
          timestamp: Date.now(),
          retries: 0,
        });
      }
    }

    for (const file of localNote.files) {
      if (file.base64Data) {
        await dbManager.addToSyncQueue({
          type: 'uploadFile',
          noteId: serverNoteId,
          data: { fileId: file.id },
          timestamp: Date.now(),
          retries: 0,
        });
      }
    }
  }

  /**
   * Запланировать повторную попытку
   */
  private scheduleRetry(): void {
    if (this.syncTimer) {
      clearTimeout(this.syncTimer);
    }

    this.syncTimer = setTimeout(() => {
      console.log('[SyncService] Retrying sync...');
      this.startSync();
    }, RETRY_DELAY_MS);
  }

  /**
   * Обновить счетчик ожидающих синхронизацию
   */
  private async updatePendingCount(): Promise<void> {
    const count = await dbManager.getPendingSyncCount();
    store.dispatch(setPendingSyncCount(count));
  }

  /**
   * Добавить listener для событий завершения синхронизации
   */
  onSyncComplete(callback: () => void): void {
    this.listeners.push(callback);
  }

  /**
   * Уведомить listeners
   */
  private notifyListeners(): void {
    this.listeners.forEach((callback) => callback());
  }

  /**
   * Остановить сервис
   */
  stop(): void {
    if (this.syncTimer) {
      clearTimeout(this.syncTimer);
      this.syncTimer = null;
    }
    this.listeners = [];
  }
}

export const syncService = new SyncService();

