/**
 * Offline Service
 *
 * Handles offline data caching, sync queue, and network state
 */

import AsyncStorage from '@react-native-async-storage/async-storage';
import NetInfo, { NetInfoState } from '@react-native-community/netinfo';
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

// Types
export interface SyncAction {
  id: string;
  timestamp: number;
  entity: 'partner' | 'product' | 'invoice';
  action: 'create' | 'update' | 'delete';
  data: Record<string, any>;
  retries: number;
  status: 'pending' | 'syncing' | 'failed';
  error?: string;
}

export interface CachedData<T> {
  data: T;
  cachedAt: number;
  expiresAt: number;
}

interface OfflineState {
  isOnline: boolean;
  lastSyncAt: number | null;
  syncQueue: SyncAction[];
  isSyncing: boolean;
  syncError: string | null;
}

interface OfflineActions {
  setOnline: (online: boolean) => void;
  addToQueue: (action: Omit<SyncAction, 'id' | 'timestamp' | 'retries' | 'status'>) => void;
  removeFromQueue: (id: string) => void;
  updateQueueItem: (id: string, updates: Partial<SyncAction>) => void;
  clearQueue: () => void;
  setSyncing: (syncing: boolean) => void;
  setSyncError: (error: string | null) => void;
  setLastSyncAt: (timestamp: number) => void;
}

// Generate unique ID
const generateId = () => `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;

// Offline Store
export const useOfflineStore = create<OfflineState & OfflineActions>()(
  persist(
    (set, get) => ({
      // State
      isOnline: true,
      lastSyncAt: null,
      syncQueue: [],
      isSyncing: false,
      syncError: null,

      // Actions
      setOnline: (online) => set({ isOnline: online }),

      addToQueue: (action) => {
        const newAction: SyncAction = {
          ...action,
          id: generateId(),
          timestamp: Date.now(),
          retries: 0,
          status: 'pending',
        };
        set((state) => ({
          syncQueue: [...state.syncQueue, newAction],
        }));
      },

      removeFromQueue: (id) =>
        set((state) => ({
          syncQueue: state.syncQueue.filter((item) => item.id !== id),
        })),

      updateQueueItem: (id, updates) =>
        set((state) => ({
          syncQueue: state.syncQueue.map((item) =>
            item.id === id ? { ...item, ...updates } : item
          ),
        })),

      clearQueue: () => set({ syncQueue: [] }),

      setSyncing: (syncing) => set({ isSyncing: syncing }),

      setSyncError: (error) => set({ syncError: error }),

      setLastSyncAt: (timestamp) => set({ lastSyncAt: timestamp }),
    }),
    {
      name: 'offline-storage',
      storage: createJSONStorage(() => AsyncStorage),
      partialize: (state) => ({
        syncQueue: state.syncQueue,
        lastSyncAt: state.lastSyncAt,
      }),
    }
  )
);

// Cache Service
const CACHE_PREFIX = 'cache_';
const DEFAULT_TTL = 5 * 60 * 1000; // 5 minutes

export const cacheService = {
  // Set cached data
  set: async <T>(key: string, data: T, ttl: number = DEFAULT_TTL): Promise<void> => {
    const cacheKey = `${CACHE_PREFIX}${key}`;
    const cached: CachedData<T> = {
      data,
      cachedAt: Date.now(),
      expiresAt: Date.now() + ttl,
    };
    await AsyncStorage.setItem(cacheKey, JSON.stringify(cached));
  },

  // Get cached data
  get: async <T>(key: string): Promise<T | null> => {
    const cacheKey = `${CACHE_PREFIX}${key}`;
    try {
      const raw = await AsyncStorage.getItem(cacheKey);
      if (!raw) return null;

      const cached: CachedData<T> = JSON.parse(raw);

      // Check if expired
      if (Date.now() > cached.expiresAt) {
        await AsyncStorage.removeItem(cacheKey);
        return null;
      }

      return cached.data;
    } catch {
      return null;
    }
  },

  // Get cached data even if expired (for offline fallback)
  getStale: async <T>(key: string): Promise<{ data: T; isStale: boolean } | null> => {
    const cacheKey = `${CACHE_PREFIX}${key}`;
    try {
      const raw = await AsyncStorage.getItem(cacheKey);
      if (!raw) return null;

      const cached: CachedData<T> = JSON.parse(raw);
      return {
        data: cached.data,
        isStale: Date.now() > cached.expiresAt,
      };
    } catch {
      return null;
    }
  },

  // Remove cached item
  remove: async (key: string): Promise<void> => {
    const cacheKey = `${CACHE_PREFIX}${key}`;
    await AsyncStorage.removeItem(cacheKey);
  },

  // Clear all cache
  clearAll: async (): Promise<void> => {
    const keys = await AsyncStorage.getAllKeys();
    const cacheKeys = keys.filter((k) => k.startsWith(CACHE_PREFIX));
    await AsyncStorage.multiRemove(cacheKeys);
  },

  // Get cache stats
  getStats: async (): Promise<{ count: number; keys: string[] }> => {
    const keys = await AsyncStorage.getAllKeys();
    const cacheKeys = keys.filter((k) => k.startsWith(CACHE_PREFIX));
    return {
      count: cacheKeys.length,
      keys: cacheKeys.map((k) => k.replace(CACHE_PREFIX, '')),
    };
  },
};

// Network Monitor
let unsubscribe: (() => void) | null = null;

export const networkMonitor = {
  // Start monitoring network state
  start: () => {
    if (unsubscribe) return;

    unsubscribe = NetInfo.addEventListener((state: NetInfoState) => {
      const isOnline = state.isConnected && state.isInternetReachable !== false;
      useOfflineStore.getState().setOnline(isOnline ?? false);
    });

    // Initial check
    NetInfo.fetch().then((state) => {
      const isOnline = state.isConnected && state.isInternetReachable !== false;
      useOfflineStore.getState().setOnline(isOnline ?? false);
    });
  },

  // Stop monitoring
  stop: () => {
    if (unsubscribe) {
      unsubscribe();
      unsubscribe = null;
    }
  },

  // Check current state
  check: async (): Promise<boolean> => {
    const state = await NetInfo.fetch();
    return (state.isConnected && state.isInternetReachable !== false) ?? false;
  },
};

// Sync Manager
export const syncManager = {
  // Process sync queue
  processQueue: async (
    handlers: {
      partner?: {
        create: (data: any) => Promise<any>;
        update: (id: string, data: any) => Promise<any>;
        delete: (id: string) => Promise<void>;
      };
      product?: {
        create: (data: any) => Promise<any>;
        update: (id: string, data: any) => Promise<any>;
        delete: (id: string) => Promise<void>;
      };
      invoice?: {
        create: (data: any) => Promise<any>;
        update: (id: string, data: any) => Promise<any>;
        delete: (id: string) => Promise<void>;
      };
    }
  ): Promise<{ success: number; failed: number }> => {
    const store = useOfflineStore.getState();

    if (store.isSyncing || !store.isOnline) {
      return { success: 0, failed: 0 };
    }

    const queue = store.syncQueue.filter((item) => item.status === 'pending');
    if (queue.length === 0) {
      return { success: 0, failed: 0 };
    }

    store.setSyncing(true);
    store.setSyncError(null);

    let success = 0;
    let failed = 0;

    for (const item of queue) {
      try {
        store.updateQueueItem(item.id, { status: 'syncing' });

        const handler = handlers[item.entity];
        if (!handler) {
          throw new Error(`No handler for entity: ${item.entity}`);
        }

        switch (item.action) {
          case 'create':
            await handler.create(item.data);
            break;
          case 'update':
            await handler.update(item.data.id, item.data);
            break;
          case 'delete':
            await handler.delete(item.data.id);
            break;
        }

        store.removeFromQueue(item.id);
        success++;
      } catch (error) {
        const errorMessage = error instanceof Error ? error.message : 'Unknown error';
        const newRetries = item.retries + 1;

        if (newRetries >= 3) {
          store.updateQueueItem(item.id, {
            status: 'failed',
            retries: newRetries,
            error: errorMessage,
          });
          failed++;
        } else {
          store.updateQueueItem(item.id, {
            status: 'pending',
            retries: newRetries,
            error: errorMessage,
          });
        }
      }
    }

    store.setSyncing(false);
    if (success > 0) {
      store.setLastSyncAt(Date.now());
    }

    return { success, failed };
  },

  // Get pending count
  getPendingCount: (): number => {
    return useOfflineStore.getState().syncQueue.filter(
      (item) => item.status === 'pending'
    ).length;
  },

  // Get failed items
  getFailedItems: (): SyncAction[] => {
    return useOfflineStore.getState().syncQueue.filter(
      (item) => item.status === 'failed'
    );
  },

  // Retry failed items
  retryFailed: (): void => {
    const store = useOfflineStore.getState();
    const failed = store.syncQueue.filter((item) => item.status === 'failed');

    for (const item of failed) {
      store.updateQueueItem(item.id, {
        status: 'pending',
        retries: 0,
        error: undefined,
      });
    }
  },
};
