/**
 * Session Guard Composable
 * 
 * Connects to /sse/stream to receive real-time updates for session validity,
 * user notes, and executive commands.
 * When the session is gone, it clears all local auth state and reloads
 * the page so the user lands on the login screen.
 * 
 * FALLBACK: If SSE is unavailable (insecure context, EventSource errors),
 * automatically switches to a Web Worker that polls /get/isloggedin and
 * /get/isExec at regular intervals.
 */
import { onMounted, onUnmounted } from 'vue';
import { useUserStore } from '../../stores/user.js';
import { usePosStore } from '../../stores/pos.js';
import { useUIStore } from '../../stores/ui.js';
import { useProductStore } from '../../stores/product.js';

const GUARD_ACTIVE_KEY = 'session_guard_active';

// Singleton guard — only one EventSource across the entire SPA
let eventSource = null;
let pollingWorker = null;
let guardStarted = false;
let sseFailCount = 0;
const MAX_SSE_RETRIES = 1;

/**
 * Clear all local authentication artifacts and force reload to login.
 * Exported so the axios interceptor can call it without duplicating logic.
 */
export function handleSessionExpired() {
  console.warn('[SessionGuard] Session expired — clearing local state and redirecting to login.');

  // Clear sessionStorage auth data
  sessionStorage.removeItem('user_acct_type');
  sessionStorage.removeItem('currentUser');
  sessionStorage.removeItem(GUARD_ACTIVE_KEY);

  // Clear any localStorage auth tokens if they exist
  localStorage.removeItem('auth_token');
  localStorage.removeItem('jwt_token');
  localStorage.removeItem('token');

  // Stop the connection
  stopGuard();

  // Force full page reload to login
  window.location.href = '/login';
}

/**
 * Start the SSE connection (idempotent — safe to call multiple times).
 * Exported so callers can re-trigger the guard after the user's account type
 * is confirmed (e.g., right after fetchCurrentUser resolves with a real user).
 */
export function startGuard() {
  if (guardStarted) return;

  // Don't connect on login/public pages — would cause redirect loops
  const path = window.location.pathname.toLowerCase();
  if (path === '/login' || path === '/sp/login') return;

  // Don't connect for guests. The guard exists to detect an *expired*
  // authenticated session — for someone who was never logged in there is
  // nothing to guard, and connecting would cause /sse/stream to immediately
  // report isloggedin:false and bounce the visitor to /login.
  const acctType = sessionStorage.getItem('user_acct_type');
  if (!acctType || acctType === 'public') return;

  guardStarted = true;
  sseFailCount = 0;
  sessionStorage.setItem(GUARD_ACTIVE_KEY, '1');

  connectSSE();
  checkExec();
}

/**
 * Fetch and execute any pending executive command once.
 */
async function checkExec() {
  try {
    const response = await fetch('/get/isExec');
    if (response.ok) {
      const command = await response.text();
      if (command && command.trim()) {
        const userStore = useUserStore();
        userStore.executeCommand(command);
      }
    }
  } catch (e) {
    console.warn('[SessionGuard] Failed to fetch initial exec command:', e);
  }
}

// Tracking the current build/app version
let currentVersion = null;

function connectSSE() {
  if (eventSource) {
    eventSource.close();
  }

  // Initialize current version from initial page if not yet set
  if (!currentVersion) {
    const el = document.getElementById('main-body');
    if (el && el.dataset.page) {
        try {
            const pageData = JSON.parse(el.dataset.page);
            currentVersion = pageData.version;
        } catch (e) {}
    }
  }

  // Check if EventSource is available at all
  if (typeof EventSource === 'undefined') {
    console.warn('[SessionGuard] EventSource not available, switching to Web Worker polling fallback.');
    startPollingFallback();
    return;
  }

  try {
    eventSource = new EventSource('/sse/stream');
  } catch (e) {
    console.warn('[SessionGuard] Failed to create EventSource, switching to Web Worker polling fallback.', e);
    startPollingFallback();
    return;
  }

  eventSource.onmessage = (event) => {
    // Reset fail count on successful message
    sseFailCount = 0;

    try {
      const data = JSON.parse(event.data);
      
      if (data.isloggedin === false) {
        handleSessionExpired();
        return;
      }
      
      // Reload page if a new asset version is detected
      if (data.version && currentVersion && data.version !== currentVersion) {
        console.warn('[SessionGuard] New asset version detected:', data.version, 'Local version:', currentVersion, '. Reloading...');
        window.location.reload();
        return;
      }
      
      // If version hasn't been set yet (shouldn't happen with the check above, but for safety)
      if (data.version && !currentVersion) {
        currentVersion = data.version;
      }

      const userStore = useUserStore();
      const posStore = usePosStore();
      const uiStore = useUIStore();
      const productStore = useProductStore();
      
      if (data.notes) {
        userStore.setNotes(data.notes);
      }
      
      if (data.exec) {
        userStore.executeCommand(data.exec);
      }

      if (data.disabled_pages) {
        uiStore.syncDisabledPages(data.disabled_pages);
      }

      if (data.pos_stats || data.customers || data.inventory_deltas) {
        posStore.syncFromSSE(data);
      }

      if (data.products_market || data.inventory_deltas) {
        productStore.syncFromSSE(data);
      }
      
    } catch (e) {
      console.error('[SessionGuard] Failed to parse SSE data:', e);
    }
  };

  eventSource.onerror = (error) => {
    sseFailCount++;
    console.warn('[SessionGuard] SSE connection error (' + sseFailCount + '/' + (MAX_SSE_RETRIES + 1) + ').', error);

    if (sseFailCount > MAX_SSE_RETRIES) {
      // SSE is not working reliably — switch to Web Worker polling
      console.warn('[SessionGuard] SSE unavailable after retries, switching to Web Worker polling fallback.');
      if (eventSource) {
        eventSource.close();
        eventSource = null;
      }
      startPollingFallback();
      return;
    }

    if (eventSource && eventSource.readyState === EventSource.CLOSED) {
      // One-time check if we're actually logged out (401)
      fetch('/get/isloggedin').then(res => {
        if (res.status === 401 || res.status === 419) {
          handleSessionExpired();
        }
      }).catch(() => {});
    }
  };
}

/**
 * Start the Web Worker polling fallback.
 * Used when SSE (EventSource) is unavailable or keeps failing.
 */
function startPollingFallback() {
  if (pollingWorker) return;

  // Check Web Worker support
  if (typeof Worker === 'undefined') {
    console.error('[SessionGuard] Neither EventSource nor Web Workers available. Falling back to setInterval polling on main thread.');
    startMainThreadFallback();
    return;
  }

  try {
    pollingWorker = new Worker(
      new URL('../../workers/session-guard-worker.js', import.meta.url),
      { type: 'classic' }
    );
  } catch (e) {
    console.warn('[SessionGuard] Failed to create Web Worker, falling back to main-thread polling.', e);
    startMainThreadFallback();
    return;
  }

  console.info('[SessionGuard] Web Worker polling fallback started.');

  pollingWorker.onmessage = (event) => {
    const { type, data } = event.data || {};

    if (type === 'session') {
      if (data && data.isloggedin === false) {
        handleSessionExpired();
        return;
      }
    }

    if (type === 'exec') {
      if (data && typeof data === 'string' && data.trim()) {
        const userStore = useUserStore();
        userStore.executeCommand(data);
      }
    }
  };

  pollingWorker.onerror = (err) => {
    console.error('[SessionGuard] Web Worker error:', err);
    // If the worker itself fails, fall back to main thread
    stopPollingWorker();
    startMainThreadFallback();
  };

  // Send start command (worker also auto-starts, but this is explicit)
  pollingWorker.postMessage({ type: 'start' });
}

/**
 * Last-resort fallback: poll on the main thread using setInterval.
 * Used when neither SSE nor Web Workers are available.
 */
let mainThreadLoginTimer = null;
let mainThreadExecTimer = null;

function startMainThreadFallback() {
  if (mainThreadLoginTimer) return;

  console.info('[SessionGuard] Main-thread polling fallback started.');

  const checkLoginMain = async () => {
    try {
      const res = await fetch('/get/isloggedin');
      if (res.ok) {
        const data = await res.json();
        if (data.isloggedin === false) {
          handleSessionExpired();
        }
      } else if (res.status === 401 || res.status === 419) {
        handleSessionExpired();
      }
    } catch (e) { /* skip */ }
  };

  const checkExecMain = async () => {
    try {
      const res = await fetch('/get/isExec');
      if (res.ok) {
        const text = await res.text();
        if (text && text.trim()) {
          const userStore = useUserStore();
          userStore.executeCommand(text);
        }
      }
    } catch (e) { /* skip */ }
  };

  // Run immediately
  checkLoginMain();
  checkExecMain();

  mainThreadLoginTimer = setInterval(checkLoginMain, 10000);
  mainThreadExecTimer = setInterval(checkExecMain, 30000);
}

function stopMainThreadFallback() {
  if (mainThreadLoginTimer) { clearInterval(mainThreadLoginTimer); mainThreadLoginTimer = null; }
  if (mainThreadExecTimer)  { clearInterval(mainThreadExecTimer);  mainThreadExecTimer  = null; }
}

function stopPollingWorker() {
  if (pollingWorker) {
    try { pollingWorker.postMessage({ type: 'stop' }); } catch (e) {}
    pollingWorker.terminate();
    pollingWorker = null;
  }
}

/**
 * Stop the SSE connection and any fallback mechanisms.
 */
function stopGuard() {
  if (eventSource) {
    eventSource.close();
    eventSource = null;
  }
  stopPollingWorker();
  stopMainThreadFallback();
  guardStarted = false;
  sseFailCount = 0;
  sessionStorage.removeItem(GUARD_ACTIVE_KEY);
}

/**
 * Vue composable to activate the session guard within a component lifecycle.
 * Typically used in the root App or a layout component.
 */
export function useSessionGuard() {
  onMounted(() => {
    startGuard();
  });

  onUnmounted(() => {
    // In SPA root this won't fire, but be tidy for sub-components
    stopGuard();
  });

  return {
    startGuard,
    stopGuard,
  };
}
