import axiosClient from "./axiosClient";
import { Note } from "../types/note";
import { dbManager } from "../utils/indexedDB";
import {
  generateTempId,
  isTempId,
  fileToBase64,
} from "../utils/offlineManager";
import { checkNetworkStatus } from "../utils/offlineManager";
import { store } from "../store/index";
import {
  setOfflineMode,
  updateNote,
  addNote,
  setPendingSyncCount,
} from "../store/slices/notesSlice";
import { NoteImage, NoteFile } from "../types/note";

/**
 * Проверка состояния сети с кэшированием
 */
let lastNetworkCheck: { time: number; status: boolean } | null = null;
const NETWORK_CHECK_CACHE_MS = 5000;

async function isOnline(): Promise<boolean> {
  const now = Date.now();
  if (
    lastNetworkCheck &&
    now - lastNetworkCheck.time < NETWORK_CHECK_CACHE_MS
  ) {
    return lastNetworkCheck.status;
  }

  try {
    const status = await checkNetworkStatus();
    lastNetworkCheck = { time: now, status };
    store.dispatch(setOfflineMode(!status));
    return status;
  } catch (error) {
    const status = navigator.onLine;
    lastNetworkCheck = { time: now, status };
    store.dispatch(setOfflineMode(!status));
    return status;
  }
}

/**
 * Получение userId из store
 */
function getUserId(): number | null {
  const state = store.getState();
  return state.auth.userId;
}

/**
 * Offline-aware API обертка
 */
export const offlineNotesApi = {
  /**
   * Получение всех заметок
   */
  getAll: async (): Promise<Note[]> => {
    const online = await isOnline();
    const userId = getUserId();

    if (!online) {
      // Offline: загружаем из IndexedDB
      console.log("[Offline] Loading notes from IndexedDB");
      const localNotes = userId
        ? await dbManager.getNotesByUserId(userId)
        : await dbManager.getAllNotes();
      return localNotes;
    }

    // Online: загружаем с сервера и кэшируем
    try {
      console.log("[Online] Loading notes from server");
      const { data } = await axiosClient.get<Note[]>("/notes");
      const notesWithSyncStatus = data.map((note) => ({
        ...note,
        syncStatus: "synced" as const,
      }));

      // Кэшируем в IndexedDB
      if (userId) {
        // Получаем текущие заметки из IndexedDB
        const localNotes = await dbManager.getNotesByUserId(userId);

        // Создаем 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(
              `[OfflineAPI] Removing deleted note from IndexedDB: ${localNote.id}`
            );
            await dbManager.deleteNote(localNote.id);
          }
        }

        // Обновляем/добавляем все актуальные заметки
        await dbManager.saveNotes(notesWithSyncStatus);
      }

      return notesWithSyncStatus;
    } catch (error) {
      console.error(
        "Error fetching notes from server, falling back to cache:",
        error
      );
      // Fallback на IndexedDB при ошибке
      const localNotes = userId
        ? await dbManager.getNotesByUserId(userId)
        : await dbManager.getAllNotes();
      return localNotes;
    }
  },

  /**
   * Поиск заметок
   */
  search: async (params: {
    q?: string;
    tag?: string;
    date?: string;
  }): Promise<Note[]> => {
    const online = await isOnline();
    const userId = getUserId();

    if (!online) {
      // Offline: загружаем из IndexedDB и фильтруем локально
      console.log("[Offline] Searching notes locally");
      const allNotes = userId
        ? await dbManager.getNotesByUserId(userId)
        : await dbManager.getAllNotes();

      return filterNotesLocally(allNotes, params);
    }

    // Online: ищем на сервере
    try {
      console.log("[Online] Searching notes on server");
      const { data } = await axiosClient.get<Note[]>("/notes/search", {
        params,
      });
      const notesWithSyncStatus = data.map((note) => ({
        ...note,
        syncStatus: "synced" as const,
      }));

      // Кэшируем результаты в IndexedDB
      if (userId) {
        await dbManager.saveNotes(notesWithSyncStatus);
      }

      return notesWithSyncStatus;
    } catch (error) {
      console.error("Error searching notes, falling back to local:", error);
      const allNotes = userId
        ? await dbManager.getNotesByUserId(userId)
        : await dbManager.getAllNotes();
      return filterNotesLocally(allNotes, params);
    }
  },

  /**
   * Создание заметки
   */
  create: async (note: {
    content: string;
    date: string;
    time: string;
    is_private?: 0 | 1;
  }): Promise<Note> => {
    const online = await isOnline();
    const userId = getUserId();

    if (!online) {
      // Offline: создаем локально с временным ID
      console.log("[Offline] Creating note locally");
      const tempId = generateTempId();
      const now = new Date().toISOString();
      const newNote: Note = {
        ...note,
        id: tempId,
        user_id: userId || 0,
        created_at: now,
        updated_at: now,
        is_pinned: 0,
        is_archived: 0,
        is_private: note.is_private !== undefined ? note.is_private : 1,
        images: [],
        files: [],
        syncStatus: "pending",
      };

      // Сохраняем в IndexedDB
      await dbManager.saveNote(newNote);

      // Добавляем в очередь синхронизации
      await dbManager.addToSyncQueue({
        type: "create",
        noteId: tempId,
        data: note,
        timestamp: Date.now(),
        retries: 0,
      });

      // Обновляем UI
      store.dispatch(addNote(newNote));
      await updatePendingSyncCount();

      return newNote;
    }

    // Online: создаем на сервере
    try {
      console.log("[Online] Creating note on server");
      const { data } = await axiosClient.post("/notes", note);
      const noteWithSyncStatus = {
        ...data,
        syncStatus: "synced" as const,
      };

      // Кэшируем
      if (userId) {
        await dbManager.saveNote(noteWithSyncStatus);
      }

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

      return noteWithSyncStatus;
    } catch (error: any) {
      // Проверяем, является ли это сетевой ошибкой
      const isNetworkError = 
        !error.response && 
        (error.code === 'ERR_NETWORK' || 
         error.message === 'Network Error' ||
         error.message?.includes('ERR_INTERNET_DISCONNECTED') ||
         error.message?.includes('Failed to fetch'));
      
      if (isNetworkError) {
        console.error("Network error creating note, falling back to local:", error);
        
        // Принудительно обновляем статус сети при ошибке
        lastNetworkCheck = { time: Date.now(), status: false };
        store.dispatch(setOfflineMode(true));
        
        // Fallback на локальное создание напрямую, без рекурсии
        console.log("[Offline] Creating note locally after network error");
        
        const tempId = generateTempId();
        const now = new Date().toISOString();
        const newNote: Note = {
          ...note,
          id: tempId,
          user_id: userId || 0,
          created_at: now,
          updated_at: now,
          is_pinned: 0,
          is_archived: 0,
          is_private: note.is_private !== undefined ? note.is_private : 1,
          images: [],
          files: [],
          syncStatus: "pending",
        };

        // Сохраняем в IndexedDB
        await dbManager.saveNote(newNote);

        // Добавляем в очередь синхронизации
        await dbManager.addToSyncQueue({
          type: "create",
          noteId: tempId,
          data: note,
          timestamp: Date.now(),
          retries: 0,
        });

        // Обновляем UI
        store.dispatch(addNote(newNote));
        await updatePendingSyncCount();

        return newNote;
      } else {
        // Если это не сетевая ошибка, пробрасываем её дальше
        console.error("Error creating note (not a network error):", error);
        throw error;
      }
    }
  },

  /**
   * Обновление заметки
   */
  update: async (
    id: number | string,
    content: string,
    skipTimestamp?: boolean,
    is_private?: 0 | 1
  ): Promise<Note> => {
    const online = await isOnline();

    if (!online) {
      // Offline: обновляем локально
      console.log("[Offline] Updating note locally");
      const existingNote = await dbManager.getNote(id);
      if (!existingNote) {
        throw new Error("Note not found");
      }

      const updatedNote: Note = {
        ...existingNote,
        content,
        is_private: is_private !== undefined ? is_private : existingNote.is_private,
        updated_at: new Date().toISOString(),
        syncStatus: "pending",
      };

      await dbManager.saveNote(updatedNote);

      // Добавляем в очередь синхронизации
      await dbManager.addToSyncQueue({
        type: "update",
        noteId: id,
        data: { content, skipTimestamp, is_private },
        timestamp: Date.now(),
        retries: 0,
      });

      store.dispatch(updateNote(updatedNote));
      await updatePendingSyncCount();

      return updatedNote;
    }

    // Online: обновляем на сервере
    try {
      console.log("[Online] Updating note on server");
      if (isTempId(id)) {
        throw new Error("Cannot update temp note online");
      }

      const { data } = await axiosClient.put(`/notes/${id}`, {
        content,
        skipTimestamp,
        is_private,
      });

      const noteWithSyncStatus = {
        ...data,
        syncStatus: "synced" as const,
      };

      await dbManager.saveNote(noteWithSyncStatus);
      store.dispatch(updateNote(noteWithSyncStatus));

      return noteWithSyncStatus;
    } catch (error) {
      console.error("Error updating note, falling back to local:", error);
      return offlineNotesApi.update(id, content, skipTimestamp);
    }
  },

  /**
   * Удаление заметки
   */
  delete: async (id: number | string): Promise<void> => {
    const online = await isOnline();

    if (!online) {
      // Offline: помечаем для удаления
      console.log("[Offline] Queuing note for deletion");
      const note = await dbManager.getNote(id);
      if (note) {
        // Оставляем заметку в БД с пометкой для удаления
        await dbManager.addToSyncQueue({
          type: "delete",
          noteId: id,
          data: {},
          timestamp: Date.now(),
          retries: 0,
        });
      }

      await updatePendingSyncCount();
      return;
    }

    // Online: удаляем на сервере
    try {
      console.log("[Online] Deleting note on server");
      if (isTempId(id)) {
        throw new Error("Cannot delete temp note online");
      }

      await axiosClient.delete(`/notes/${id}`);
      await dbManager.deleteNote(id);
    } catch (error) {
      console.error("Error deleting note, falling back to local:", error);
      await offlineNotesApi.delete(id);
    }
  },

  /**
   * Закрепление заметки
   */
  pin: async (id: number | string): Promise<Note> => {
    const online = await isOnline();

    if (!online) {
      const note = await dbManager.getNote(id);
      if (!note) {
        throw new Error("Note not found");
      }

      const updatedNote: Note = {
        ...note,
        is_pinned: note.is_pinned === 0 ? 1 : 0,
        pinned_at: note.is_pinned === 0 ? new Date().toISOString() : undefined,
        updated_at: new Date().toISOString(),
        syncStatus: "pending",
      };

      await dbManager.saveNote(updatedNote);

      // Добавляем в очередь синхронизации
      await dbManager.addToSyncQueue({
        type: "update",
        noteId: id,
        data: { content: note.content, is_pinned: updatedNote.is_pinned },
        timestamp: Date.now(),
        retries: 0,
      });

      store.dispatch(updateNote(updatedNote));
      await updatePendingSyncCount();

      return updatedNote;
    }

    try {
      if (isTempId(id)) {
        throw new Error("Cannot pin temp note online");
      }

      const { data } = await axiosClient.put(`/notes/${id}/pin`);

      // Получаем текущую заметку и обновляем её
      const currentNote = await dbManager.getNote(id);
      if (!currentNote) {
        throw new Error("Note not found");
      }

      const noteWithSyncStatus = {
        ...currentNote,
        is_pinned: data.is_pinned,
        pinned_at: data.is_pinned ? new Date().toISOString() : undefined,
        syncStatus: "synced" as const,
      };

      await dbManager.saveNote(noteWithSyncStatus);
      store.dispatch(updateNote(noteWithSyncStatus));

      return noteWithSyncStatus;
    } catch (error) {
      console.error("Error pinning note:", error);
      throw error;
    }
  },

  /**
   * Архивирование заметки
   */
  archive: async (id: number | string): Promise<Note> => {
    const online = await isOnline();

    if (!online) {
      const note = await dbManager.getNote(id);
      if (!note) {
        throw new Error("Note not found");
      }

      const updatedNote: Note = {
        ...note,
        is_archived: 1,
        updated_at: new Date().toISOString(),
        syncStatus: "pending",
      };

      await dbManager.saveNote(updatedNote);

      // Добавляем в очередь синхронизации
      await dbManager.addToSyncQueue({
        type: "update",
        noteId: id,
        data: { content: note.content, is_archived: 1 },
        timestamp: Date.now(),
        retries: 0,
      });

      store.dispatch(updateNote(updatedNote));
      await updatePendingSyncCount();

      return updatedNote;
    }

    try {
      if (isTempId(id)) {
        throw new Error("Cannot archive temp note online");
      }

      await axiosClient.put(`/notes/${id}/archive`);

      // Получаем текущую заметку и обновляем её
      const currentNote = await dbManager.getNote(id);
      if (!currentNote) {
        throw new Error("Note not found");
      }

      const noteWithSyncStatus: Note = {
        ...currentNote,
        is_archived: 1 as const,
        is_pinned: 0 as const,
        pinned_at: undefined,
        syncStatus: "synced" as const,
      };

      await dbManager.saveNote(noteWithSyncStatus);
      store.dispatch(updateNote(noteWithSyncStatus));

      return noteWithSyncStatus;
    } catch (error) {
      console.error("Error archiving note:", error);
      throw error;
    }
  },

  /**
   * Разархивирование заметки
   */
  unarchive: async (id: number | string): Promise<Note> => {
    const online = await isOnline();

    if (!online) {
      const note = await dbManager.getNote(id);
      if (!note) {
        throw new Error("Note not found");
      }

      const updatedNote: Note = {
        ...note,
        is_archived: 0,
        updated_at: new Date().toISOString(),
        syncStatus: "pending",
      };

      await dbManager.saveNote(updatedNote);

      // Добавляем в очередь синхронизации
      await dbManager.addToSyncQueue({
        type: "update",
        noteId: id,
        data: { content: note.content, is_archived: 0 },
        timestamp: Date.now(),
        retries: 0,
      });

      store.dispatch(updateNote(updatedNote));
      await updatePendingSyncCount();

      return updatedNote;
    }

    try {
      if (isTempId(id)) {
        throw new Error("Cannot unarchive temp note online");
      }

      await axiosClient.put(`/notes/${id}/unarchive`);

      // Получаем текущую заметку и обновляем её
      const currentNote = await dbManager.getNote(id);
      if (!currentNote) {
        throw new Error("Note not found");
      }

      const noteWithSyncStatus: Note = {
        ...currentNote,
        is_archived: 0 as const,
        syncStatus: "synced" as const,
      };

      await dbManager.saveNote(noteWithSyncStatus);
      store.dispatch(updateNote(noteWithSyncStatus));

      return noteWithSyncStatus;
    } catch (error) {
      console.error("Error unarchiving note:", error);
      throw error;
    }
  },

  /**
   * Загрузка изображений
   */
  uploadImages: async (
    noteId: number | string,
    files: File[]
  ): Promise<NoteImage[]> => {
    const online = await isOnline();

    if (!online) {
      // Offline: конвертируем в base64 и сохраняем локально
      console.log("[Offline] Converting images to base64");
      const note = await dbManager.getNote(noteId);
      if (!note) {
        throw new Error("Note not found");
      }

      const images: NoteImage[] = [];
      for (const file of files) {
        const base64 = await fileToBase64(file);
        const image: NoteImage = {
          id: generateTempId(),
          note_id: noteId,
          filename: file.name,
          original_name: file.name,
          file_path: "",
          file_size: file.size,
          mime_type: file.type,
          created_at: new Date().toISOString(),
          base64Data: base64,
        };
        images.push(image);
      }

      const updatedNote: Note = {
        ...note,
        images: [...note.images, ...images],
        syncStatus: "pending",
      };

      await dbManager.saveNote(updatedNote);

      // Добавляем в очередь синхронизации
      for (const image of images) {
        await dbManager.addToSyncQueue({
          type: "uploadImage",
          noteId: noteId,
          data: { imageId: image.id },
          timestamp: Date.now(),
          retries: 0,
        });
      }

      store.dispatch(updateNote(updatedNote));
      await updatePendingSyncCount();

      return images;
    }

    // Online: загружаем на сервер
    try {
      if (isTempId(noteId)) {
        throw new Error("Cannot upload images for temp note online");
      }

      const formData = new FormData();
      files.forEach((file) => formData.append("images", file));

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

      // Сервер возвращает объект с полем images
      const uploadedImages = Array.isArray(data) ? data : (data?.images || []);

      const note = await dbManager.getNote(noteId);
      if (note) {
        const updatedNote: Note = {
          ...note,
          images: [...(note.images || []), ...uploadedImages],
          syncStatus: "synced",
        };
        await dbManager.saveNote(updatedNote);
        store.dispatch(updateNote(updatedNote));
      }

      return uploadedImages;
    } catch (error) {
      console.error("Error uploading images:", error);
      throw error;
    }
  },

  /**
   * Загрузка файлов
   */
  uploadFiles: async (
    noteId: number | string,
    files: File[]
  ): Promise<NoteFile[]> => {
    const online = await isOnline();

    if (!online) {
      // Offline: конвертируем в base64 и сохраняем локально
      console.log("[Offline] Converting files to base64");
      const note = await dbManager.getNote(noteId);
      if (!note) {
        throw new Error("Note not found");
      }

      const noteFiles: NoteFile[] = [];
      for (const file of files) {
        const base64 = await fileToBase64(file);
        const noteFile: NoteFile = {
          id: generateTempId(),
          note_id: noteId,
          filename: file.name,
          original_name: file.name,
          file_path: "",
          file_size: file.size,
          mime_type: file.type,
          created_at: new Date().toISOString(),
          base64Data: base64,
        };
        noteFiles.push(noteFile);
      }

      const updatedNote: Note = {
        ...note,
        files: [...note.files, ...noteFiles],
        syncStatus: "pending",
      };

      await dbManager.saveNote(updatedNote);

      // Добавляем в очередь синхронизации
      for (const file of noteFiles) {
        await dbManager.addToSyncQueue({
          type: "uploadFile",
          noteId: noteId,
          data: { fileId: file.id },
          timestamp: Date.now(),
          retries: 0,
        });
      }

      store.dispatch(updateNote(updatedNote));
      await updatePendingSyncCount();

      return noteFiles;
    }

    // Online: загружаем на сервер
    try {
      if (isTempId(noteId)) {
        throw new Error("Cannot upload files for temp note online");
      }

      const formData = new FormData();
      files.forEach((file) => formData.append("files", file));

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

      // Сервер возвращает объект с полем files
      const uploadedFiles = Array.isArray(data) ? data : (data?.files || []);

      const note = await dbManager.getNote(noteId);
      if (note) {
        const updatedNote: Note = {
          ...note,
          files: [...(note.files || []), ...uploadedFiles],
          syncStatus: "synced",
        };
        await dbManager.saveNote(updatedNote);
        store.dispatch(updateNote(updatedNote));
      }

      return uploadedFiles;
    } catch (error) {
      console.error("Error uploading files:", error);
      throw error;
    }
  },

  /**
   * Удаление изображения
   */
  deleteImage: async (
    noteId: number | string,
    imageId: number | string
  ): Promise<void> => {
    const online = await isOnline();

    if (!online) {
      const note = await dbManager.getNote(noteId);
      if (note) {
        const updatedNote: Note = {
          ...note,
          images: note.images.filter((img) => img.id !== imageId),
          syncStatus: "pending",
        };
        await dbManager.saveNote(updatedNote);
        store.dispatch(updateNote(updatedNote));
        await updatePendingSyncCount();
      }
      return;
    }

    try {
      await axiosClient.delete(`/notes/${noteId}/images/${imageId}`);
      const note = await dbManager.getNote(noteId);
      if (note) {
        const updatedNote: Note = {
          ...note,
          images: note.images.filter((img) => img.id !== imageId),
          syncStatus: "synced",
        };
        await dbManager.saveNote(updatedNote);
        store.dispatch(updateNote(updatedNote));
      }
    } catch (error) {
      console.error("Error deleting image:", error);
      throw error;
    }
  },

  /**
   * Удаление файла
   */
  deleteFile: async (
    noteId: number | string,
    fileId: number | string
  ): Promise<void> => {
    const online = await isOnline();

    if (!online) {
      const note = await dbManager.getNote(noteId);
      if (note) {
        const updatedNote: Note = {
          ...note,
          files: note.files.filter((file) => file.id !== fileId),
          syncStatus: "pending",
        };
        await dbManager.saveNote(updatedNote);
        store.dispatch(updateNote(updatedNote));
        await updatePendingSyncCount();
      }
      return;
    }

    try {
      await axiosClient.delete(`/notes/${noteId}/files/${fileId}`);
      const note = await dbManager.getNote(noteId);
      if (note) {
        const updatedNote: Note = {
          ...note,
          files: note.files.filter((file) => file.id !== fileId),
          syncStatus: "synced",
        };
        await dbManager.saveNote(updatedNote);
        store.dispatch(updateNote(updatedNote));
      }
    } catch (error) {
      console.error("Error deleting file:", error);
      throw error;
    }
  },

  /**
   * Получение архивированных заметок
   */
  getArchived: async (): Promise<Note[]> => {
    const online = await isOnline();
    const userId = getUserId();

    if (!online) {
      const allNotes = userId
        ? await dbManager.getNotesByUserId(userId)
        : await dbManager.getAllNotes();
      return allNotes.filter((note) => note.is_archived === 1);
    }

    try {
      const { data } = await axiosClient.get<Note[]>("/notes/archived");
      const notesWithSyncStatus = data.map((note) => ({
        ...note,
        syncStatus: "synced" as const,
      }));
      return notesWithSyncStatus;
    } catch (error) {
      console.error("Error fetching archived notes:", error);
      const allNotes = userId
        ? await dbManager.getNotesByUserId(userId)
        : await dbManager.getAllNotes();
      return allNotes.filter((note) => note.is_archived === 1);
    }
  },

  deleteArchived: async (id: number | string) => {
    const online = await isOnline();

    if (!online) {
      await offlineNotesApi.delete(id);
      return;
    }

    try {
      await axiosClient.delete(`/notes/archived/${id}`);
      await dbManager.deleteNote(id);
    } catch (error) {
      console.error("Error deleting archived note:", error);
      throw error;
    }
  },

  deleteAllArchived: async (password: string) => {
    const online = await isOnline();

    if (!online) {
      throw new Error("Cannot delete all archived in offline mode");
    }

    try {
      const { data } = await axiosClient.delete("/notes/archived/all", {
        data: { password },
      });
      return data;
    } catch (error) {
      console.error("Error deleting all archived:", error);
      throw error;
    }
  },
};

/**
 * Вспомогательная функция для локальной фильтрации
 */
function filterNotesLocally(
  notes: Note[],
  params: { q?: string; tag?: string; date?: string }
): Note[] {
  let filtered = notes;

  if (params.q) {
    const query = params.q.toLowerCase();
    filtered = filtered.filter((note) =>
      note.content.toLowerCase().includes(query)
    );
  }

  if (params.tag) {
    const tag = params.tag.toLowerCase();
    filtered = filtered.filter((note) => {
      const tags = extractTags(note.content);
      return tags.some((t) => t.toLowerCase() === tag);
    });
  }

  if (params.date) {
    filtered = filtered.filter((note) => note.date === params.date);
  }

  return filtered;
}

function extractTags(content: string): string[] {
  const tagRegex = /#(\w+)/g;
  const tags: string[] = [];
  let match;
  while ((match = tagRegex.exec(content)) !== null) {
    tags.push(match[1]);
  }
  return tags;
}

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