// HTTP Polling with chrome.alarms - Keeps service worker alive in Manifest V3
const attachedTabs = new Set();
const PORT = 9876;
let lastCommandId = null;
let isRegistered = false;

// Persistent state management - load state on startup
chrome.storage.session.get(['isRegistered', 'lastCommandId']).then(state => {
  isRegistered = state.isRegistered || false;
  lastCommandId = state.lastCommandId || null;
  if (isRegistered) {
    log('info', '🔄 Restored registration state from storage');
  }
});

// Enhanced logging with timestamps
function log(level, message, ...args) {
  const timestamp = new Date().toISOString().split('T')[1].split('Z')[0];
  const prefix = `[${timestamp}] [CHROME_EXT]`;

  switch(level) {
    case 'error':
      console.error(`${prefix} ❌`, message, ...args);
      break;
    case 'warn':
      console.warn(`${prefix} ⚠️`, message, ...args);
      break;
    case 'info':
      console.info(`${prefix} ℹ️`, message, ...args);
      break;
    case 'debug':
      console.log(`${prefix} 🔍`, message, ...args);
      break;
    default:
      console.log(`${prefix}`, message, ...args);
  }
}

// Set up alarm to check for commands every 3 seconds (balanced for responsiveness)
// Adding jitter to prevent thundering herd problem
const baseInterval = 3; // seconds
const jitter = Math.floor(Math.random() * 1); // 0-1 second jitter
const intervalWithJitter = baseInterval + jitter;
chrome.alarms.create('pollCommands', { periodInMinutes: intervalWithJitter / 60 }); // Convert to minutes
log('info', `Chrome extension initialized - polling every ${intervalWithJitter} seconds`);

// Handle alarm
chrome.alarms.onAlarm.addListener(async (alarm) => {
  if (alarm.name === 'pollCommands') {
    await checkForCommands();
  }
});

// Check for commands
async function checkForCommands() {
  // Auto-register if not registered yet
  if (!isRegistered) {
    log('warn', 'Not registered yet, attempting registration...');
    await registerWithServer();
    // If still not registered after attempt, skip this poll
    if (!isRegistered) {
      return;
    }
  }

  try {
    log('debug', `Polling server at http://localhost:${PORT}/poll`);

    const response = await fetch(`http://localhost:${PORT}/poll`, {
      method: 'GET',
      headers: { 'Content-Type': 'application/json' }
    });

    if (response.status === 204) {
      // No commands available - this is normal
      log('debug', 'No commands in queue (204)');
      return;
    }

    if (!response.ok) {
      log('error', `Server error: ${response.status} ${response.statusText}`);
      return;
    }
    
    const command = await response.json();
    
    // Skip if we already processed this command
    if (command.id === lastCommandId) {
      log('debug', `Skipping duplicate command: ${command.id}`);
      return;
    }

    lastCommandId = command.id;
    // Persist last command ID
    await chrome.storage.session.set({ lastCommandId: command.id });
    log('info', `📥 Received command: ${command.id}`, {
      command: command.command,
      paramsSize: JSON.stringify(command.params).length
    });
    
    // Process the command
    let result;
    if (command.command === 'execute') {
      log('info', '🚀 Executing browser automation code...');
      const startTime = Date.now();
      result = await executeChrome(command.params.code);
      const duration = Date.now() - startTime;
      const resultLength =
        typeof result === 'string'
          ? result.length
          : JSON.stringify(result ?? {}).length;
      log('info', `✅ Execution completed in ${duration}ms`, { resultLength });
    } else {
      log('warn', `Unknown command type: ${command.command}`);
      result = {
        success: false,
        error: `Unknown command type: ${command.command}`,
        executed: new Date().toISOString()
      };
    }
    
    // Send result back
    try {
      const completedAt = new Date().toISOString();
      const success = typeof result?.success === 'boolean' ? result.success : true;
      const responsePayload = {
        id: command.id,
        success,
        result,
        completed_at: completedAt
      };

      if (!success) {
        responsePayload.error = result?.error || 'Unknown browser automation error';
      }

      log('debug', `Sending response for command ${command.id} (success: ${success})...`);
      const resp = await fetch(`http://localhost:${PORT}/response`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(responsePayload)
      });

      if (resp.ok) {
        log('info', `📤 Response sent successfully for command ${command.id}`);
      } else {
        log('error', `Failed to send response: ${resp.status} ${resp.statusText}`);
      }
    } catch (error) {
      log('error', 'Failed to send response:', error);
    }
  } catch (error) {
    log('error', 'Polling error:', error);
    // If it's a network error, the server might be down
    if (error.message && error.message.includes('Failed to fetch')) {
      log('warn', 'Server appears to be down - will retry on next poll');
    }
  }
}

// Register with server on startup
async function registerWithServer() {
  try {
    log('info', `🔌 Attempting to register with server at http://localhost:${PORT}/register`);

    const response = await fetch(`http://localhost:${PORT}/register`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        type: 'extension',
        mode: 'alarms',
        version: chrome.runtime.getManifest().version,
        userAgent: navigator.userAgent
      })
    });

    if (response.ok) {
      isRegistered = true;
      // Persist registration state
      await chrome.storage.session.set({ isRegistered: true });
      log('info', '✅ Successfully registered with Sonatic browser server!');
      log('info', 'Starting command polling with chrome.alarms (3-4s interval)');
    } else {
      isRegistered = false;
      // Clear registration state on failure
      await chrome.storage.session.set({ isRegistered: false });
      log('error', `Registration failed: ${response.status} ${response.statusText}`);
      // Don't retry here - will retry on next poll
    }
  } catch (error) {
    isRegistered = false;
    // Clear registration state on failure
    await chrome.storage.session.set({ isRegistered: false });
    log('error', 'Failed to connect to Sonatic server:', error);
    log('warn', 'Make sure Sonatic app is running (./run.sh)');
    // Don't retry here - will retry on next poll
  }
}

// Helper function for chrome.scripting.executeScript with enhanced error handling
async function runJS(tabId, code) {
  try {
    // Only attach if not already attached to this tab
    if (!attachedTabs.has(tabId)) {
      try {
        await chrome.debugger.attach({ tabId }, '1.3');
        attachedTabs.add(tabId);
      } catch (attachErr) {
        // If already attached, just add to set
        if (attachErr.message.includes('already attached')) {
          attachedTabs.add(tabId);
        } else {
          throw attachErr;
        }
      }
    }

    // Wrap the user's code to catch runtime errors and ensure a return value
    const wrappedCode = `
      (async function() {
        try {
          const __result = await (async function() {
            ${code}
          })();
          // If result is undefined, return a clear indicator
          if (__result === undefined) {
            return {__undefined: true, message: 'Script returned undefined (no explicit return statement)'};
          }
          return __result;
        } catch (error) {
          // Return runtime errors as structured data
          return {
            __error: true,
            message: error.message || String(error),
            stack: error.stack,
            name: error.name
          };
        }
      })()
    `;

    // Execute with a timeout wrapper
    const timeoutPromise = new Promise((_, reject) => {
      setTimeout(() => reject(new Error('Script execution timeout (30 seconds)')), 30000);
    });

    const executePromise = chrome.debugger.sendCommand({ tabId }, 'Runtime.evaluate', {
      expression: wrappedCode,
      returnByValue: true,
      awaitPromise: true,
      includeCommandLineAPI: true,  // Enables $, $$ shortcuts
      silent: false  // Show console output
    });

    const res = await Promise.race([executePromise, timeoutPromise]);

    // Check for exception details (syntax errors, etc.)
    if (res.exceptionDetails) {
      return {
        error: res.exceptionDetails.text || 'Script execution error',
        exception: {
          description: res.exceptionDetails.exception?.description,
          lineNumber: res.exceptionDetails.lineNumber,
          columnNumber: res.exceptionDetails.columnNumber
        }
      };
    }

    // Get the actual result value
    const resultValue = res.result?.value;

    // Handle special cases from our wrapper
    if (resultValue && typeof resultValue === 'object') {
      if (resultValue.__error) {
        return {
          error: resultValue.message,
          stack: resultValue.stack,
          name: resultValue.name
        };
      }
      if (resultValue.__undefined) {
        return {
          result: null,
          warning: resultValue.message
        };
      }
    }

    // Return the actual result
    return {
      result: resultValue !== undefined ? resultValue : res.result?.description || null,
      code
    };
  } catch (err) {
    // Handle timeout or connection errors
    if (err.message && err.message.includes('timeout')) {
      return { error: 'Script execution timeout (30 seconds)' };
    }
    if (err.message && err.message.includes('not attached')) {
      attachedTabs.delete(tabId);
    }
    return { error: err.message || String(err) };
  }
}

// ULTIMATE POWER MODE - Execute any Chrome API code dynamically
async function executeChrome(code) {
  // Don't log full code payload for security - use preview only
  try {
    const preview = typeof code === 'string' ? code.slice(0, 100) : '[object]';
    log('debug', `executeChrome typeof=${typeof code} preview=${preview}...`);
  } catch (previewErr) {
    log('warn', 'executeChrome preview logging failed', previewErr);
  }
  
  if (!code) {
    return {
      success: false,
      error: 'No code provided',
      executed: new Date().toISOString()
    };
  }

  let structuredInstruction = null;
  if (typeof code === 'string') {
    const trimmed = code.trim();
    if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
      try {
        structuredInstruction = JSON.parse(trimmed);
        log('debug', 'Parsed structured browser instruction', structuredInstruction);
      } catch (parseError) {
        console.warn('Failed to parse structured browser instruction:', parseError.message);
      }
    }
  }

  if (structuredInstruction?.action === 'capture_screenshot') {
    const tabId = structuredInstruction.tab_id ?? structuredInstruction.tabId;
    if (typeof tabId !== 'number') {
      return {
        success: false,
        error: 'capture_screenshot requires numeric tab_id',
        executed: new Date().toISOString()
      };
    }

    const format = structuredInstruction.format || 'png';
    let attachedForScreenshot = false;

    try {
      if (!attachedTabs.has(tabId)) {
        try {
          await chrome.debugger.attach({ tabId }, '1.3');
          attachedTabs.add(tabId);
          attachedForScreenshot = true;
          log('info', `Debugger attached to tab ${tabId} for screenshot capture`);
        } catch (attachErr) {
          if (attachErr?.message?.includes('already attached')) {
            log('warn', `Debugger already attached to tab ${tabId}, reusing session`);
          } else {
            throw attachErr;
          }
        }
      }

      const screenshot = await chrome.debugger.sendCommand(
        { tabId },
        'Page.captureScreenshot',
        { format }
      );

      const rawData = screenshot?.data;
      if (!rawData) {
        throw new Error('Page.captureScreenshot returned no data');
      }

      const dataUrl = `data:image/${format};base64,${rawData}`;

      return {
        success: true,
        result: {
          dataUrl,
          tabId,
          format,
          capturedAt: new Date().toISOString()
        },
        executed: new Date().toISOString(),
        meta: {
          action: 'capture_screenshot',
          via: 'chrome.debugger'
        }
      };
    } catch (error) {
      console.error('Screenshot capture failed:', error);
      return {
        success: false,
        error: error.message || String(error),
        executed: new Date().toISOString()
      };
    } finally {
      if (attachedForScreenshot) {
        try {
          await chrome.debugger.detach({ tabId });
        } catch (detachErr) {
          console.warn('Failed to detach debugger after screenshot:', detachErr);
        }
        attachedTabs.delete(tabId);
      }
    }
  }
  
  try {
    // Check if this is a Chrome API call pattern: chrome.something.method(args)
    const chromeApiPattern = /^chrome\.([a-zA-Z.]+)\.([a-zA-Z]+)\((.*)\)$/s;
    const match = code.trim().match(chromeApiPattern);
    
    if (match) {
      const [, apiPath, methodName, argsString] = match;
      console.log('Detected Chrome API call:', { apiPath, methodName });

      const isExecuteScript = apiPath === 'scripting' && methodName === 'executeScript';

      // Helper to run the legacy sanitation + parse flow when needed
      const parseWithSanitizer = (rawArgs) => {
        let processedArgs = rawArgs.trim();
        processedArgs = processedArgs.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":');
        return JSON.parse('[' + processedArgs + ']');
      };

      // Parse arguments - favor direct JSON for chrome.scripting.executeScript payloads
      let args = [];
      if (argsString.trim()) {
        try {
          if (isExecuteScript) {
            args = JSON.parse('[' + argsString.trim() + ']');
            console.log('Parsed executeScript arguments without sanitizer');
          } else {
            args = parseWithSanitizer(argsString);
            console.log('Successfully parsed arguments:', args);
          }
        } catch (error) {
          if (isExecuteScript) {
            console.warn('Direct JSON parse failed for executeScript payload, falling back to sanitizer:', error.message);
            try {
              args = parseWithSanitizer(argsString);
            } catch (fallbackErr) {
              console.error('Failed to parse executeScript arguments even after sanitizer:', fallbackErr.message);
              throw new Error(`Failed to parse arguments: ${argsString}`);
            }
          } else {
            console.error('Failed to parse arguments as JSON:', error.message);
            throw new Error(`Failed to parse arguments: ${argsString}`);
          }
        }
      }

      // Special handling for chrome.scripting.executeScript
      if (isExecuteScript) {
        console.log('Special handling for chrome.scripting.executeScript');

        const tabId = args[0]?.target?.tabId;
        if (!tabId) {
          throw new Error('tabId is required in target for chrome.scripting.executeScript');
        }

        let codeToExecute = '';
        if (args[0].func && typeof args[0].func === 'string') {
          codeToExecute = `(${args[0].func})()`;
        } else if (args[0].code && typeof args[0].code === 'string') {
          codeToExecute = args[0].code;
        } else {
          throw new Error('chrome.scripting.executeScript requires either a "func" or "code" property as a string');
        }

        const result = await runJS(tabId, codeToExecute);

        return {
          success: !result.error,
          result: result.error ? null : [{
            result: result.result,
            documentId: 'executed-via-debugger'
          }],
          error: result.error,
          executed: new Date().toISOString(),
          method: 'scripting-executeScript-via-debugger',
          api: `${apiPath}.${methodName}`
        };
      }
      
      // Navigate to the API endpoint
      const apiParts = apiPath.split('.');
      let api = chrome;
      for (const part of apiParts) {
        api = api[part];
        if (!api) {
          throw new Error(`API ${apiPath} not found`);
        }
      }
      
      // Get the method
      const method = api[methodName];
      if (!method) {
        throw new Error(`Method ${methodName} not found in ${apiPath}`);
      }
      
      // Execute the API call
      console.log('Executing Chrome API:', { apiPath, methodName, args });
      
      // Check if this returns a Promise or uses callbacks
      const result = await new Promise((resolve, reject) => {
        try {
          // Try to call with callback
          const callbackIndex = args.length;
          args[callbackIndex] = (result) => {
            if (chrome.runtime.lastError) {
              reject(chrome.runtime.lastError);
            } else {
              resolve(result);
            }
          };
          
          const returnValue = method.apply(api, args);
          
          // If it returns a Promise, use that instead
          if (returnValue && typeof returnValue.then === 'function') {
            args.splice(callbackIndex, 1); // Remove the callback
            returnValue.then(resolve).catch(reject);
          }
        } catch (error) {
          // Try without callback (sync or promise-based)
          try {
            const returnValue = method.apply(api, args);
            if (returnValue && typeof returnValue.then === 'function') {
              returnValue.then(resolve).catch(reject);
            } else {
              resolve(returnValue);
            }
          } catch (e) {
            reject(e);
          }
        }
      });
      
      return {
        success: true,
        result: result,
        executed: new Date().toISOString()
      };
    } else {
      // For non-standard code, we can't use eval due to CSP restrictions
      throw new Error('Only chrome.* API calls are supported in Manifest V3 due to CSP restrictions');
    }
  } catch (error) {
    console.error('Execution error:', error);
    return {
      success: false,
      error: error.message || String(error),
      executed: new Date().toISOString()
    };
  }
}

// Handle debugger events
chrome.debugger.onDetach.addListener((source, reason) => {
  console.log('Debugger detached:', source, reason);
  attachedTabs.delete(source.tabId);
});

// Start connection when extension loads - IMMEDIATE registration
log('info', 'Extension starting - attempting immediate registration');
registerWithServer().then(() => {
  if (isRegistered) {
    log('info', '🎉 Extension registered on startup!');
  } else {
    log('warn', 'Initial registration failed - will retry during polling');
  }
});

// Reset registration status when service worker wakes up
chrome.runtime.onStartup.addListener(() => {
  log('info', 'Service worker started up - attempting registration');
  isRegistered = false;
  registerWithServer().then(() => {
    if (isRegistered) {
      log('info', '🎉 Extension registered after startup!');
    }
  });
});

chrome.runtime.onInstalled.addListener(() => {
  log('info', 'Extension installed/updated - attempting registration');
  isRegistered = false;
  registerWithServer().then(() => {
    if (isRegistered) {
      log('info', '🎉 Extension registered after install/update!');
    }
  });
});

console.log('Sonatic Browser Control Extension loaded - Using chrome.alarms for 3-4s polling');
