import { MESSAGE_SOURCE } from "./constants.js";
import { runtime as browserRuntime, storage as browserStorage } from "./browser.js";

/**
 * ROBUST error message extraction for browser runtime errors
 * runtime.lastError is a special object that must be handled carefully
 */
function extractErrorMessage(error) {
  if (!error) return 'Unknown error';
  
  // CRITICAL: For chrome.runtime.lastError, access .message DIRECTLY first
  // This is a getter property that Chrome provides
  try {
    // Direct property access - this is the most reliable way
    if (error.message !== undefined && error.message !== null) {
      const msg = String(error.message);
      if (msg && msg.trim()) {
        return msg;
      }
    }
  } catch (e) {
    // Property access failed, continue to other methods
  }
  
  // Handle string errors
  if (typeof error === 'string') {
    return error;
  }
  
  // Try direct property access for common error properties
  try {
    if (error.code !== undefined) {
      return `Error code: ${error.code}`;
    }
    if (error.name !== undefined) {
      return `Error: ${error.name}`;
    }
  } catch (e) {
    // Property access failed
  }
  
  // Try toString() ONLY if it's not the default Object.toString
  if (error.toString && typeof error.toString === 'function') {
    try {
      const str = error.toString();
      // Check if it's a meaningful string (not the default Object.toString)
      if (str && 
          str !== '[object Object]' && 
          str !== '[object Error]' &&
          str !== 'Error' &&
          str.length > 10) { // Must be more than just "Error"
        return str;
      }
    } catch (e) {
      // toString() failed or threw
    }
  }
  
  // Try to manually extract properties (for debugging)
  try {
    const extracted = {};
    const props = ['message', 'code', 'name', 'stack', 'description'];
    let foundAny = false;
    
    for (const prop of props) {
      try {
        if (error[prop] !== undefined) {
          extracted[prop] = String(error[prop]);
          foundAny = true;
        }
      } catch (e) {
        // Can't access this property
      }
    }
    
    if (foundAny) {
      // Prefer message if available
      if (extracted.message) {
        return extracted.message;
      }
      // Otherwise return formatted object
      return JSON.stringify(extracted);
    }
  } catch (e) {
    // Extraction failed
  }
  
  // Last resort: return descriptive error
  const errorType = error.constructor?.name || typeof error || 'Unknown';
  return `Browser Runtime Error (${errorType}) - Unable to extract message. Check runtime.lastError.message directly.`;
}

/**
 * Safely sends a message to the extension runtime
 * Handles extension context invalidation errors gracefully
 */
export function sendMessage(message, timeoutMs = 30000) {
  return new Promise((resolve, reject) => {
    try {
      // Check if runtime is available
      if (!isRuntimeAvailable()) {
        reject(new Error("Extension context invalidated. Please reload the page."));
        return;
      }

      // Double-check runtime exists before using it
      if (!browserRuntime || typeof browserRuntime.sendMessage !== 'function') {
        reject(new Error("Extension context invalidated. Please reload the page."));
        return;
      }

      // Set up timeout to prevent hanging forever
      let timeoutId = null;
      let responded = false;
      let callbackFired = false;

      const cleanup = () => {
        if (timeoutId) {
          clearTimeout(timeoutId);
          timeoutId = null;
        }
        responded = true;
      };

      timeoutId = setTimeout(() => {
        if (!responded && !callbackFired) {
          // Only timeout if callback hasn't fired yet
          // This prevents race conditions where callback fires but promise already rejected
          cleanup();
          console.error("[XRP Universe Runtime] Message timeout:", {
            method: message.method,
            id: message.id,
            timeoutMs,
            callbackFired: callbackFired,
          });
          reject(new Error(`Extension did not respond within ${timeoutMs}ms. Please try again or reload the page.`));
        } else if (!responded && callbackFired) {
          // Callback fired but promise not resolved yet - give it a bit more time
          console.warn("[XRP Universe Runtime] Timeout fired but callback already fired, waiting a bit longer:", {
            method: message.method,
            id: message.id,
          });
          // Don't reject yet - callback might still resolve
        }
      }, timeoutMs);

      // Wake up the service worker before sending the message
      // This ensures the service worker is active and ready to receive the message
      // For provider.connect, we wait a bit longer to ensure SW is active
      const wakeUpTimeout = message.method === "provider.connect" ? 100 : 50;
      ensureServiceWorkerActive(wakeUpTimeout).catch(() => {
        // Ignore wake-up errors - we'll still try to send the message
        console.warn("[XRP Universe Runtime] Service worker wake-up failed, but continuing");
      });

      console.log("[XRP Universe Runtime] Sending message via runtime.sendMessage:", {
        method: message.method,
        id: message.id,
        runtimeId: browserRuntime.id,
      });

      browserRuntime.sendMessage(message, (response) => {
        // CRITICAL: Mark callback as fired immediately
        callbackFired = true;
        
        // CRITICAL: Log callback timing immediately
        const callbackTime = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
        console.log("[Content] <- BG callback", message.id, callbackTime);
        
        // CRITICAL: Check runtime.lastError IMMEDIATELY and extract message DIRECTLY
        // Don't try to serialize the object - access .message property directly
        let lastErrorMsg = null;
        let errorType = 'none';
        if (browserRuntime.lastError) {
          // Direct property access - this is the ONLY reliable way
          try {
            lastErrorMsg = browserRuntime.lastError.message || null;
            errorType = typeof browserRuntime.lastError;
          } catch (e) {
            // If direct access fails, use extraction function
            lastErrorMsg = extractErrorMessage(browserRuntime.lastError);
            errorType = 'extracted';
          }
          
          // Extract direct message safely - NEVER log the error object directly
          const directMsg = (() => {
            try {
              return browserRuntime.lastError?.message || null;
            } catch (e) {
              return null;
            }
          })();
          
          // Only log strings - never the error object itself
          const errorString = lastErrorMsg || directMsg || 'UNKNOWN ERROR - Unable to extract message';
          const directMsgString = directMsg || 'NO MESSAGE PROPERTY';
          
          console.error("[XRP Universe Runtime] Immediate error in sendMessage callback:", 
            `method: ${message.method}, ` +
            `id: ${message.id}, ` +
            `error: ${errorString}, ` +
            `errorMessageDirect: ${directMsgString}, ` +
            `errorType: ${errorType}, ` +
            `timestamp: ${new Date().toISOString()}`
          );
        }
        
        console.log("[XRP Universe Runtime] runtime.sendMessage callback invoked:", {
          method: message.method,
          id: message.id,
          hasResponse: !!response,
          responseValue: response,
          responseKeys: response ? Object.keys(response) : [],
          hasError: !!browserRuntime.lastError,
          errorMessage: lastErrorMsg || browserRuntime.lastError?.message || 'none',
          responseType: response ? typeof response : 'null',
          responded: responded,
        });
        if (responded) {
          console.log("[XRP Universe Runtime] Callback fired but already responded (timeout), ignoring");
          return; // Already handled by timeout
        }
        
        cleanup();
        
        try {
          // Handle extension context invalidation
          if (browserRuntime.lastError) {
            // Extract error message - try direct access first
            let errorMsg = null;
            try {
              errorMsg = browserRuntime.lastError.message || null;
            } catch (e) {
              errorMsg = extractErrorMessage(browserRuntime.lastError);
            }
            
            // If direct access didn't work, use extraction function
            if (!errorMsg) {
              errorMsg = extractErrorMessage(browserRuntime.lastError);
            }
            
            // Extract direct message safely - NEVER log the object
            const directMsg = (() => {
              try {
                return browserRuntime.lastError?.message || null;
              } catch (e) {
                return null;
              }
            })();
            
            // Only log strings - never the error object itself
            const errorString = errorMsg || directMsg || 'UNKNOWN ERROR - Unable to extract message';
            const directMsgString = directMsg || 'NO MESSAGE PROPERTY';
            const errorTypeString = (() => {
              try {
                return typeof browserRuntime.lastError;
              } catch (e) {
                return 'unknown';
              }
            })();
            
            console.error("[XRP Universe Runtime] runtime.lastError detected:", 
              `method: ${message.method}, ` +
              `id: ${message.id}, ` +
              `error: ${errorString}, ` +
              `errorMessageDirect: ${directMsgString}, ` +
              `errorType: ${errorTypeString}, ` +
              `timestamp: ${new Date().toISOString()}`
            );
            
            if (errorMsg.includes("Extension context invalidated") || 
                errorMsg.includes("message port closed") ||
                errorMsg.includes("Receiving end does not exist") ||
                errorMsg.includes("No SW") ||
                errorMsg.includes("Could not establish connection") ||
                errorMsg.includes("The message port closed") ||
                errorMsg.includes("Could not establish connection. Receiving end does not exist") ||
                errorMsg.includes("message channel closed") ||
                errorMsg.includes("A listener indicated an asynchronous response")) {
              reject(new Error("Extension context invalidated. Please reload the page."));
              return;
            }
            
            reject(new Error(errorMsg || "Runtime error"));
            return;
          }

          if (!response) {
            console.error("[XRP Universe Runtime] No response received:", {
              method: message.method,
              id: message.id,
              hasLastError: !!browserRuntime.lastError,
              responseValue: response,
              responseType: typeof response,
            });
            reject(new Error("No response from extension. The extension may not be responding. Please check if the extension service worker is running."));
            return;
          }

          console.log("[XRP Universe Runtime] Resolving promise with response:", {
            method: message.method,
            id: message.id,
            responseOk: response?.ok,
            hasResult: !!response?.result,
            hasError: !!response?.error,
          });
          resolve(response);
        } catch (error) {
          // Catch any errors that occur during response handling
          reject(new Error("Extension context invalidated. Please reload the page."));
        }
      });
    } catch (error) {
      // If accessing chrome.runtime throws, context is invalidated
      reject(new Error("Extension context invalidated. Please reload the page."));
    }
  });
}

/**
 * Safely gets the runtime URL
 */
export function getRuntimeURL(path) {
  try {
    if (!browserRuntime?.getURL) {
      throw new Error("Extension runtime is not available");
    }
    return browserRuntime.getURL(path);
  } catch (error) {
    console.error("Failed to get runtime URL:", error);
    return path;
  }
}

/**
 * Attempts to wake up the service worker before sending messages
 * This helps ensure the service worker is active and ready
 */
async function ensureServiceWorkerActive(timeoutMs = 3000) {
  if (!isRuntimeAvailable()) {
    return false;
  }
  
  try {
    // Try multiple wake-up methods
    const wakeUpPromises = [
      browserStorage.local.set({ 'wakeUp': Date.now() }).catch(() => {}),
      browserStorage.session.set({ 'wakeUp': Date.now() }).catch(() => {}),
    ];
    
    await Promise.allSettled(wakeUpPromises);
    
    // Try to ping service worker to verify it's active
    try {
      await new Promise((resolve, reject) => {
        const timeout = setTimeout(() => {
          // Timeout is okay - service worker might not respond to ping
          // but it could still be active
          resolve(false);
        }, timeoutMs);
        
        browserRuntime.sendMessage(
          { source: MESSAGE_SOURCE, method: 'ping', id: 'wakeup-check' },
          (response) => {
            clearTimeout(timeout);
            if (browserRuntime.lastError) {
              // Error is okay - service worker might still wake up
              resolve(false);
            } else {
              resolve(true);
            }
          }
        );
      });
    } catch (error) {
      // Ping failed, but that's okay - we'll still try to send the message
      console.warn('[XRP Universe] Service worker ping failed, but continuing:', extractErrorMessage(error));
    }
    
    return true;
  } catch (error) {
    console.warn('[XRP Universe] Service worker wake-up failed:', extractErrorMessage(error));
    return false;
  }
}

/**
 * Checks if the extension runtime is available
 */
export function isRuntimeAvailable() {
  try {
    // Check if runtime exists and is accessible
    if (!browserRuntime) {
      return false;
    }
    // Check if runtime.id exists (this will throw if context is invalidated)
    if (!browserRuntime.id) {
      return false;
    }
    // Check if sendMessage method exists
    if (typeof browserRuntime.sendMessage !== 'function') {
      return false;
    }
    return true;
  } catch (error) {
    // If any access throws an error, context is invalidated
    return false;
  }
}

