/**
 * Service Worker for LandSalesPro
 * https://landsales.co.tz
 * Enables offline functionality, caching, and push notifications
 */

const CACHE_VERSION = 'v1.0.1';
const CACHE_NAME = `landsalespro-${CACHE_VERSION}`;
const DATA_CACHE_NAME = `landsalespro-data-${CACHE_VERSION}`;
const IMAGE_CACHE_NAME = `landsalespro-images-${CACHE_VERSION}`;

// Detect base path (handles localhost/landsales/ vs production /)
const getBasePath = () => {
    const scope = self.registration.scope;
    const url = new URL(scope);
    // Return the pathname without trailing slash, or empty for root
    let path = url.pathname.replace(/\/$/, '');
    return path;
};

// Core files to cache for offline use (relative paths)
const RELATIVE_ASSETS = [
    '/',
    '/offline.html',
    '/login.php',
    '/dashboard/',
    '/dashboard/index.php',
    '/manifest.json',
    '/assets/vendor/bootstrap/css/bootstrap.min.css',
    '/assets/vendor/jquery/jquery.min.js',
    '/assets/vendor/bootstrap/js/bootstrap.bundle.min.js',
    '/assets/font-awesome/css/all.min.css',
    '/assets/js/pwa.js',
    '/assets/css/pwa.css',
    '/assets/icons/icon-192x192.png',
    '/assets/icons/icon-512x512.png'
];

// Build full paths based on scope
const getStaticAssets = () => {
    const basePath = getBasePath();
    return RELATIVE_ASSETS.map(asset => basePath + asset);
};

// Cache duration settings (in seconds)
const CACHE_DURATIONS = {
    static: 60 * 60 * 24 * 30, // 30 days
    api: 60 * 5,               // 5 minutes
    images: 60 * 60 * 24 * 7   // 7 days
};

// Install event - cache static assets
self.addEventListener('install', event => {
    console.log('[SW] Installing Service Worker', CACHE_VERSION);
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => {
                console.log('[SW] Caching static assets');
                const assets = getStaticAssets();
                return Promise.allSettled(
                    assets.map(url =>
                        cache.add(url).catch(err => {
                            console.log(`[SW] Failed to cache: ${url}`, err.message);
                        })
                    )
                );
            })
            .then(() => self.skipWaiting())
    );
});

// Activate event - clean up old caches
self.addEventListener('activate', event => {
    console.log('[SW] Activating Service Worker', CACHE_VERSION);
    event.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames.map(cacheName => {
                    if (cacheName.startsWith('landsalespro-') &&
                        cacheName !== CACHE_NAME &&
                        cacheName !== DATA_CACHE_NAME &&
                        cacheName !== IMAGE_CACHE_NAME) {
                        console.log('[SW] Deleting old cache:', cacheName);
                        return caches.delete(cacheName);
                    }
                })
            );
        }).then(() => {
            return self.clients.claim();
        })
    );
});

// Fetch event - serve from cache, fallback to network
self.addEventListener('fetch', event => {
    const requestUrl = new URL(event.request.url);

    // Skip non-GET requests for caching
    if (event.request.method !== 'GET') {
        if (event.request.method === 'POST') {
            event.respondWith(handlePostRequest(event.request));
        }
        return;
    }

    // Skip chrome-extension and other non-http(s) requests
    if (!requestUrl.protocol.startsWith('http')) {
        return;
    }

    // Handle API requests differently
    if (isApiRequest(event.request)) {
        event.respondWith(handleApiRequest(event.request));
        return;
    }

    // Handle image requests
    if (isImageRequest(event.request)) {
        event.respondWith(handleImageRequest(event.request));
        return;
    }

    // For navigation and other requests, use network-first strategy
    event.respondWith(
        fetch(event.request)
            .then(response => {
                if (response.status === 200) {
                    const responseClone = response.clone();
                    caches.open(CACHE_NAME).then(cache => {
                        cache.put(event.request, responseClone);
                    });
                }
                return response;
            })
            .catch(() => {
                return caches.match(event.request)
                    .then(cachedResponse => {
                        if (cachedResponse) {
                            return cachedResponse;
                        }
                        if (event.request.mode === 'navigate') {
                            const basePath = getBasePath();
                            return caches.match(basePath + '/offline.html');
                        }
                        return new Response('Offline', {
                            status: 503,
                            statusText: 'Service Unavailable'
                        });
                    });
            })
    );
});

// Check if request is to an API endpoint
function isApiRequest(request) {
    const url = new URL(request.url);
    return url.pathname.includes('/ajax/') ||
           url.pathname.includes('/api/') ||
           request.headers.get('Accept')?.includes('application/json');
}

// Check if request is for an image
function isImageRequest(request) {
    const url = new URL(request.url);
    return url.pathname.match(/\.(jpg|jpeg|png|gif|webp|svg|ico)$/i);
}

// Handle API requests with network-first strategy
async function handleApiRequest(request) {
    try {
        const response = await fetch(request);

        if (response.status === 200) {
            const cache = await caches.open(DATA_CACHE_NAME);
            cache.put(request, response.clone());

            notifyClients({
                type: 'API_RESPONSE_CACHED',
                url: request.url
            });
        }

        return response;
    } catch (error) {
        console.log('[SW] Network request failed, trying cache:', request.url);

        const cachedResponse = await caches.match(request);
        if (cachedResponse) {
            const headers = new Headers(cachedResponse.headers);
            headers.set('X-SW-Cache', 'true');
            return new Response(cachedResponse.body, {
                status: cachedResponse.status,
                statusText: cachedResponse.statusText,
                headers: headers
            });
        }

        return new Response(JSON.stringify({
            offline: true,
            message: 'You are offline. Data will sync when you reconnect.',
            cached: false
        }), {
            status: 200,
            headers: { 'Content-Type': 'application/json' }
        });
    }
}

// Handle image requests with cache-first strategy
async function handleImageRequest(request) {
    const cachedResponse = await caches.match(request);
    if (cachedResponse) {
        fetch(request).then(response => {
            if (response.status === 200) {
                caches.open(IMAGE_CACHE_NAME).then(cache => {
                    cache.put(request, response);
                });
            }
        }).catch(() => {});

        return cachedResponse;
    }

    try {
        const response = await fetch(request);
        if (response.status === 200) {
            const cache = await caches.open(IMAGE_CACHE_NAME);
            cache.put(request, response.clone());
        }
        return response;
    } catch (error) {
        return new Response('', {
            status: 404,
            statusText: 'Image not found'
        });
    }
}

// Handle POST requests - store offline if network fails
async function handlePostRequest(request) {
    try {
        const response = await fetch(request.clone());
        return response;
    } catch (error) {
        console.log('[SW] POST request failed, queueing for sync');

        const requestData = await serializeRequest(request);

        notifyClients({
            type: 'OFFLINE_POST_REQUEST',
            data: requestData
        });

        return new Response(JSON.stringify({
            offline: true,
            queued: true,
            message: 'Request saved. Will sync when you are back online.'
        }), {
            status: 200,
            headers: { 'Content-Type': 'application/json' }
        });
    }
}

// Serialize a request for storage
async function serializeRequest(request) {
    const headers = {};
    for (const [key, value] of request.headers.entries()) {
        headers[key] = value;
    }

    let body = null;
    if (request.method !== 'GET' && request.method !== 'HEAD') {
        const contentType = request.headers.get('Content-Type') || '';
        if (contentType.includes('application/json')) {
            body = await request.json();
        } else if (contentType.includes('form')) {
            const formData = await request.formData();
            body = {};
            for (const [key, value] of formData.entries()) {
                body[key] = value;
            }
        } else {
            body = await request.text();
        }
    }

    return {
        url: request.url,
        method: request.method,
        headers: headers,
        body: body,
        timestamp: Date.now()
    };
}

// Notify all clients
async function notifyClients(message) {
    const clients = await self.clients.matchAll();
    clients.forEach(client => {
        client.postMessage(message);
    });
}

// Background Sync
self.addEventListener('sync', event => {
    console.log('[SW] Background sync event:', event.tag);

    if (event.tag === 'sync-data') {
        event.waitUntil(syncOfflineData());
    }

    if (event.tag === 'sync-sales') {
        event.waitUntil(notifyClients({ type: 'SYNC_SALES' }));
    }

    if (event.tag === 'sync-payments') {
        event.waitUntil(notifyClients({ type: 'SYNC_PAYMENTS' }));
    }
});

// Sync offline data
async function syncOfflineData() {
    notifyClients({ type: 'TRIGGER_SYNC' });
}

// Push notification handler
self.addEventListener('push', event => {
    console.log('[SW] Push received:', event);

    let data = {};
    if (event.data) {
        try {
            data = event.data.json();
        } catch (e) {
            data = { body: event.data.text() };
        }
    }

    const basePath = getBasePath();
    const options = {
        body: data.body || 'New notification from LandSalesPro',
        icon: basePath + '/assets/icons/icon-192x192.png',
        badge: basePath + '/assets/icons/badge-72x72.png',
        vibrate: [100, 50, 100],
        tag: data.tag || 'notification',
        renotify: true,
        requireInteraction: data.requireInteraction || false,
        data: {
            url: data.url || basePath + '/dashboard/',
            ...data
        },
        actions: data.actions || [
            { action: 'view', title: 'View' },
            { action: 'dismiss', title: 'Dismiss' }
        ]
    };

    event.waitUntil(
        self.registration.showNotification(data.title || 'LandSalesPro', options)
    );
});

// Notification click handler
self.addEventListener('notificationclick', event => {
    console.log('[SW] Notification clicked:', event.action);

    event.notification.close();

    if (event.action === 'dismiss') {
        return;
    }

    const basePath = getBasePath();
    const urlToOpen = event.notification.data?.url || basePath + '/dashboard/';

    event.waitUntil(
        clients.matchAll({ type: 'window', includeUncontrolled: true })
            .then(windowClients => {
                for (let client of windowClients) {
                    if (client.url.includes('landsales') && 'focus' in client) {
                        client.navigate(urlToOpen);
                        return client.focus();
                    }
                }
                return clients.openWindow(urlToOpen);
            })
    );
});

// Message handler for client communication
self.addEventListener('message', event => {
    console.log('[SW] Message received:', event.data);

    switch (event.data.type) {
        case 'SKIP_WAITING':
            self.skipWaiting();
            break;

        case 'CACHE_URLS':
            const urls = event.data.urls;
            caches.open(CACHE_NAME).then(cache => {
                cache.addAll(urls).catch(err => console.log('[SW] Cache failed:', err));
            });
            break;

        case 'CLEAR_CACHE':
            Promise.all([
                caches.delete(CACHE_NAME),
                caches.delete(DATA_CACHE_NAME),
                caches.delete(IMAGE_CACHE_NAME)
            ]).then(() => {
                event.ports[0]?.postMessage({ success: true });
            });
            break;

        case 'GET_CACHE_SIZE':
            getCacheSize().then(size => {
                event.ports[0]?.postMessage({ size: size });
            });
            break;

        case 'CACHE_PROJECT_DATA':
            cacheProjectData(event.data.projectId);
            break;
    }
});

// Get total cache size
async function getCacheSize() {
    let totalSize = 0;
    const cacheNames = await caches.keys();

    for (const cacheName of cacheNames) {
        if (cacheName.startsWith('landsalespro-')) {
            const cache = await caches.open(cacheName);
            const requests = await cache.keys();

            for (const request of requests) {
                const response = await cache.match(request);
                if (response) {
                    const blob = await response.blob();
                    totalSize += blob.size;
                }
            }
        }
    }

    return totalSize;
}

// Pre-cache project data for offline viewing
async function cacheProjectData(projectId) {
    const basePath = getBasePath();
    const urls = [
        `${basePath}/dashboard/projects/view.php?id=${projectId}`,
        `${basePath}/dashboard/plots/?project_id=${projectId}`,
        `${basePath}/dashboard/ajax/get_plots.php?project_id=${projectId}`,
        `${basePath}/dashboard/ajax/get_sales.php?project_id=${projectId}`
    ];

    const cache = await caches.open(DATA_CACHE_NAME);

    for (const url of urls) {
        try {
            const response = await fetch(url);
            if (response.ok) {
                await cache.put(url, response);
            }
        } catch (error) {
            console.log(`[SW] Failed to cache project data: ${url}`, error);
        }
    }
}

console.log('[SW] LandSalesPro Service Worker loaded');
