import System from '@/lib/models/System';

/**
 * SyncService - Handles bidirectional synchronization between local DB and server
 * Implements conflict resolution and retry logic
 */
export class SyncService {
    private syncInterval: NodeJS.Timeout | null = null;
    private isSyncing: boolean = false;
    private isOnline: boolean = navigator.onLine;
    private accessToken: string | null = null;

    constructor() {
        // Listen to online/offline events
        if (typeof window !== 'undefined') {
            window.addEventListener('online', () => {
                this.isOnline = true;
                this.onlineStatusChanged(true);
            });

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

    /**
     * Start automatic sync every interval
     * @param intervalMs - Sync interval in milliseconds (default 30 seconds)
     */
    startAutoSync(intervalMs: number = 30000): void {
        if (this.syncInterval) {
            clearInterval(this.syncInterval);
        }

        this.syncInterval = setInterval(() => {
            if (this.isOnline && !this.isSyncing) {
                this.sync();
            }
        }, intervalMs);

        console.log(`Auto-sync started with interval: ${intervalMs}ms`);
    }

    /**
     * Stop automatic sync
     */
    stopAutoSync(): void {
        if (this.syncInterval) {
            clearInterval(this.syncInterval);
            this.syncInterval = null;
        }
        console.log('Auto-sync stopped');
    }

    /**
     * Set access token for API requests
     */
    setAccessToken(token: string): void {
        this.accessToken = token;
    }

    /**
     * Check if currently online
     */
    getOnlineStatus(): boolean {
        return this.isOnline;
    }

    /**
     * Force set online/offline status (for manual toggle)
     */
    setOnlineStatus(online: boolean): void {
        this.isOnline = online;
        this.onlineStatusChanged(online);
    }

    /**
     * Handle online/offline status change
     */
    private onlineStatusChanged(online: boolean): void {
        console.log(`Network status changed: ${online ? 'ONLINE' : 'OFFLINE'}`);

        if (online && !this.isSyncing) {
            // When going online, trigger immediate sync
            setTimeout(() => this.sync(), 1000);
        }

        // Notify listeners (will be implemented in OfflineContext)
        if (typeof window !== 'undefined') {
            window.dispatchEvent(new CustomEvent('offline-status-changed', { detail: { online } }));
        }
    }

    /**
     * Perform full bidirectional sync
     */
    async sync(): Promise<SyncResult> {
        if (!this.isOnline) {
            return {
                success: false,
                error: 'Cannot sync while offline',
                pushedChanges: 0,
                pulledChanges: 0
            };
        }

        if (this.isSyncing) {
            return {
                success: false,
                error: 'Sync already in progress',
                pushedChanges: 0,
                pulledChanges: 0
            };
        }

        this.isSyncing = true;
        console.log('Starting sync...');

        try {
            // Check Electron API availability
            if (typeof window === 'undefined' || !(window as any).electron) {
                throw new Error('Electron API not available');
            }

            // Step 1: Push local changes to server
            const pushedChanges = await this.pushChanges();

            // Step 2: Pull server changes to local
            const pulledChanges = await this.pullChanges();

            console.log(`Sync completed: pushed ${pushedChanges}, pulled ${pulledChanges} changes`);

            // Dispatch sync completion event
            if (typeof window !== 'undefined') {
                window.dispatchEvent(new CustomEvent('sync-completed', {
                    detail: { pushedChanges, pulledChanges }
                }));
            }

            return {
                success: true,
                pushedChanges,
                pulledChanges
            };
        } catch (error) {
            console.error('Sync failed:', error);
            return {
                success: false,
                error: error instanceof Error ? error.message : 'Unknown error',
                pushedChanges: 0,
                pulledChanges: 0
            };
        } finally {
            this.isSyncing = false;
        }
    }

    /**
     * Push local changes to server
     */
    private async pushChanges(): Promise<number> {
        if (!this.accessToken) {
            console.warn('No access token available for sync');
            return 0;
        }

        // Get pending changes via Electron IPC
        const result = await (window as any).electron.dbGetPendingChanges(50);
        if (!result.success) {
            console.error('Failed to get pending changes:', result.error);
            return 0;
        }

        const pendingChanges = result.data || [];

        if (pendingChanges.length === 0) {
            return 0;
        }

        console.log(`Pushing ${pendingChanges.length} pending changes...`);

        let successCount = 0;
        const syncedIds: number[] = [];

        for (const change of pendingChanges) {
            try {
                const success = await this.pushSingleChange(change);
                if (success) {
                    successCount++;
                    syncedIds.push(change.id);
                }
            } catch (error) {
                console.error(`Failed to push change ${change.id}:`, error);
                // Continue with next change
            }
        }

        // Mark successfully synced changes via IPC
        if (syncedIds.length > 0) {
            // TODO: Add IPC handler for marking synced
            console.log('Synced changes:', syncedIds);
        }

        return successCount;
    }

    /**
     * Push a single change to server
     */
    private async pushSingleChange(change: any): Promise<boolean> {
        if (!this.accessToken) return false;

        const { table_name, operation, record_id, data } = change;
        let url = '';
        let method: 'POST' | 'PUT' | 'DELETE' = 'POST';

        // Map table names to API endpoints
        switch (table_name) {
            case 'erit_books':
                url = operation === 'DELETE' ? `books/${record_id}` : 'books';
                method = operation === 'DELETE' ? 'DELETE' : operation === 'INSERT' ? 'POST' : 'PUT';
                break;

            case 'book_chapters':
                url = operation === 'DELETE' ? `chapters/${record_id}` : 'chapters';
                method = operation === 'DELETE' ? 'DELETE' : operation === 'INSERT' ? 'POST' : 'PUT';
                break;

            case 'book_characters':
                url = operation === 'DELETE' ? `characters/${record_id}` : 'characters';
                method = operation === 'DELETE' ? 'DELETE' : operation === 'INSERT' ? 'POST' : 'PUT';
                break;

            case 'ai_conversations':
                url = operation === 'DELETE' ? `ai/conversations/${record_id}` : 'ai/conversations';
                method = operation === 'DELETE' ? 'DELETE' : operation === 'INSERT' ? 'POST' : 'PUT';
                break;

            default:
                console.warn(`Unknown table for sync: ${table_name}`);
                return false;
        }

        try {
            if (method === 'DELETE') {
                await System.authDeleteToServer(url, {}, this.accessToken);
            } else if (method === 'PUT') {
                await System.authPutToServer(url, JSON.parse(data), this.accessToken);
            } else {
                await System.authPostToServer(url, JSON.parse(data), this.accessToken);
            }

            return true;
        } catch (error) {
            console.error(`Failed to sync ${table_name} ${operation}:`, error);
            return false;
        }
    }

    /**
     * Pull changes from server
     */
    private async pullChanges(): Promise<number> {
        if (!this.accessToken) {
            console.warn('No access token available for sync');
            return 0;
        }

        // Get sync status via Electron IPC
        const statusResult = await (window as any).electron.dbGetSyncStatus();
        if (!statusResult.success) {
            console.error('Failed to get sync status:', statusResult.error);
            return 0;
        }

        const syncStatus = statusResult.data || [];

        let totalPulled = 0;

        // Pull updates for each table
        for (const status of syncStatus) {
            try {
                const count = await this.pullTableChanges(status.table, status.lastSync);
                totalPulled += count;
            } catch (error) {
                console.error(`Failed to pull changes for ${status.table}:`, error);
            }
        }

        return totalPulled;
    }

    /**
     * Pull changes for a specific table
     */
    private async pullTableChanges(tableName: string, lastSync: number): Promise<number> {
        if (!this.accessToken) return 0;

        // Map table names to API endpoints
        let endpoint = '';

        switch (tableName) {
            case 'erit_books':
                endpoint = 'books';
                break;
            case 'book_chapters':
                endpoint = 'chapters';
                break;
            case 'book_characters':
                endpoint = 'characters';
                break;
            case 'ai_conversations':
                endpoint = 'ai/conversations';
                break;
            default:
                return 0;
        }

        try {
            // Request changes since last sync
            const response = await System.authGetQueryToServer<any>(
                `${endpoint}/sync?since=${lastSync}`,
                this.accessToken
            );

            if (!response || !response.data) {
                return 0;
            }

            const changes = Array.isArray(response.data) ? response.data : [response.data];

            // Apply changes to local database
            // This would require implementing merge logic for each table
            // For now, we'll just log the changes

            console.log(`Pulled ${changes.length} changes for ${tableName}`);

            // Update last sync time via IPC
            // TODO: Add IPC handler for updating last sync

            return changes.length;
        } catch (error) {
            console.error(`Failed to pull changes for ${tableName}:`, error);
            return 0;
        }
    }

    /**
     * Resolve conflicts between local and server data
     * Strategy: Server wins (can be customized)
     */
    private resolveConflict(localData: any, serverData: any): any {
        // Simple strategy: server wins
        // TODO: Implement more sophisticated conflict resolution
        console.warn('Conflict detected, using server data');
        return serverData;
    }

    /**
     * Get sync progress
     */
    getSyncProgress(): SyncProgress {
        // This will be called synchronously, so we return cached state
        // The actual sync status is updated via events
        return {
            isSyncing: this.isSyncing,
            pendingChanges: 0, // Will be updated via IPC
            isOnline: this.isOnline
        };
    }
}

export interface SyncResult {
    success: boolean;
    error?: string;
    pushedChanges: number;
    pulledChanges: number;
}

export interface SyncProgress {
    isSyncing: boolean;
    pendingChanges: number;
    isOnline: boolean;
    tables?: { table: string; lastSync: number; pending: number }[];
}

// Singleton instance
let syncServiceInstance: SyncService | null = null;

export function getSyncService(): SyncService {
    if (!syncServiceInstance) {
        syncServiceInstance = new SyncService();
    }
    return syncServiceInstance;
}
