/**
 * Browser Logger - Captures all browser-side activity
 *
 * Records:
 * - Console logs (log, warn, error, info, debug)
 * - Uncaught errors and exceptions
 * - Network requests (via fetch/XMLHttpRequest)
 * - Performance metrics
 * - Memory usage
 * - User interactions
 *
 * Can be exported to server or retrieved from sessionStorage
 */

class BrowserLogger {
  constructor(maxEntries = 1000) {
    this.maxEntries = maxEntries;
    this.entries = [];
    this.startTime = Date.now();
    this.sessionId = this.generateSessionId();
    this.lastSyncedIndex = 0; // Track which logs have been sent to server
    this.autoSyncInterval = 30000; // 30 seconds
    this.apiEndpoint = '/api/browser-logs';
    this.lastUrl = window.location.href; // Track URL for navigation detection

    // Storage key for persistence across page reloads
    this.storageKey = `dss-browser-logs-${this.sessionId}`;

    // Core Web Vitals tracking
    this.lcp = null;  // Largest Contentful Paint
    this.cls = 0;     // Cumulative Layout Shift
    this.axeLoadingPromise = null;  // Promise for axe-core script loading

    // Try to load existing logs
    this.loadFromStorage();

    // Start capturing
    this.captureConsole();
    this.captureErrors();
    this.captureNetworkActivity();
    this.capturePerformance();
    this.captureMemory();
    this.captureWebVitals();

    // Initialize Shadow State capture
    this.setupSnapshotCapture();

    // Start auto-sync to server
    this.startAutoSync();
  }

  /**
   * Generate unique session ID
   */
  generateSessionId() {
    return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  /**
   * Add log entry
   */
  log(level, category, message, data = {}) {
    const entry = {
      timestamp: Date.now(),
      relativeTime: Date.now() - this.startTime,
      level,
      category,
      message,
      data,
      url: window.location.href,
      userAgent: navigator.userAgent,
    };

    this.entries.push(entry);

    // Keep size manageable
    if (this.entries.length > this.maxEntries) {
      this.entries.shift();
    }

    // Persist to storage
    this.saveToStorage();

    return entry;
  }

  /**
   * Capture console methods
   */
  captureConsole() {
    const originalLog = console.log;
    const originalError = console.error;
    const originalWarn = console.warn;
    const originalInfo = console.info;
    const originalDebug = console.debug;

    console.log = (...args) => {
      this.log('log', 'console', args.join(' '), { args });
      originalLog.apply(console, args);
    };

    console.error = (...args) => {
      this.log('error', 'console', args.join(' '), { args });
      originalError.apply(console, args);
    };

    console.warn = (...args) => {
      this.log('warn', 'console', args.join(' '), { args });
      originalWarn.apply(console, args);
    };

    console.info = (...args) => {
      this.log('info', 'console', args.join(' '), { args });
      originalInfo.apply(console, args);
    };

    console.debug = (...args) => {
      this.log('debug', 'console', args.join(' '), { args });
      originalDebug.apply(console, args);
    };
  }

  /**
   * Capture uncaught errors
   */
  captureErrors() {
    // Unhandled promise rejections
    window.addEventListener('unhandledrejection', (event) => {
      this.log('error', 'unhandledRejection', event.reason?.message || String(event.reason), {
        reason: event.reason,
        stack: event.reason?.stack,
      });
    });

    // Global error handler
    window.addEventListener('error', (event) => {
      this.log('error', 'uncaughtError', event.message, {
        filename: event.filename,
        lineno: event.lineno,
        colno: event.colno,
        stack: event.error?.stack,
      });
    });
  }

  /**
   * Capture network activity using PerformanceObserver
   * This is non-invasive and doesn't monkey-patch fetch or XMLHttpRequest
   */
  captureNetworkActivity() {
    // Use PerformanceObserver to monitor network requests (modern approach)
    if ('PerformanceObserver' in window) {
      try {
        const observer = new PerformanceObserver((list) => {
          for (const entry of list.getEntries()) {
            // resource entries are generated automatically for fetch/xhr
            if (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest') {
              const method = entry.name.split('?')[0]; // Extract method from name if available

              this.log('network', entry.initiatorType, `${entry.initiatorType.toUpperCase()} ${entry.name}`, {
                url: entry.name,
                initiatorType: entry.initiatorType,
                duration: entry.duration,
                transferSize: entry.transferSize,
                encodedBodySize: entry.encodedBodySize,
                decodedBodySize: entry.decodedBodySize,
              });
            }
          }
        });

        // Observe resource entries (includes fetch/xhr)
        observer.observe({ entryTypes: ['resource'] });
      } catch (e) {
        // PerformanceObserver might not support resource entries in some browsers
        // Gracefully degrade - network logging simply won't work
      }
    }
  }

  /**
   * Capture performance metrics
   */
  capturePerformance() {
    // Wait for page load
    window.addEventListener('load', () => {
      setTimeout(() => {
        try {
          const perfData = window.performance.getEntriesByType('navigation')[0];
          if (perfData) {
            this.log('metric', 'performance', 'Page load completed', {
              domContentLoaded: perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart,
              loadComplete: perfData.loadEventEnd - perfData.loadEventStart,
              totalTime: perfData.loadEventEnd - perfData.fetchStart,
              dnsLookup: perfData.domainLookupEnd - perfData.domainLookupStart,
              tcpConnection: perfData.connectEnd - perfData.connectStart,
              requestTime: perfData.responseStart - perfData.requestStart,
              responseTime: perfData.responseEnd - perfData.responseStart,
              renderTime: perfData.domInteractive - perfData.domLoading,
            });
          }
        } catch (e) {
          // Performance API might not be available
        }
      }, 0);
    });

    // Monitor long tasks
    if ('PerformanceObserver' in window) {
      try {
        const observer = new PerformanceObserver((list) => {
          for (const entry of list.getEntries()) {
            if (entry.duration > 50) {
              // Log tasks that take >50ms
              this.log('metric', 'longTask', 'Long task detected', {
                name: entry.name,
                duration: entry.duration,
                startTime: entry.startTime,
              });
            }
          }
        });
        observer.observe({ entryTypes: ['longtask'] });
      } catch (e) {
        // Long task API might not be available
      }
    }
  }

  /**
   * Capture memory usage
   */
  captureMemory() {
    if ('memory' in performance) {
      // Check memory every 10 seconds
      setInterval(() => {
        const memory = performance.memory;
        const usagePercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100;

        if (usagePercent > 80) {
          this.log('warn', 'memory', 'High memory usage detected', {
            usedJSHeapSize: memory.usedJSHeapSize,
            jsHeapSizeLimit: memory.jsHeapSizeLimit,
            usagePercent: usagePercent.toFixed(2),
          });
        }
      }, 10000);
    }
  }

  /**
   * Capture Core Web Vitals (LCP, CLS) using PerformanceObserver
   * These observers run in the background to collect metrics as they occur.
   */
  captureWebVitals() {
    try {
      // Capture Largest Contentful Paint (LCP)
      const lcpObserver = new PerformanceObserver((entryList) => {
        const entries = entryList.getEntries();
        if (entries.length > 0) {
          // The last entry is the most recent LCP candidate
          this.lcp = entries[entries.length - 1].startTime;
        }
      });
      lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true });

      // Capture Cumulative Layout Shift (CLS)
      const clsObserver = new PerformanceObserver((entryList) => {
        for (const entry of entryList.getEntries()) {
          // Only count shifts that were not caused by recent user input.
          if (!entry.hadRecentInput) {
            this.cls += entry.value;
          }
        }
      });
      clsObserver.observe({ type: 'layout-shift', buffered: true });
    } catch (e) {
      this.log('warn', 'performance', 'Could not initialize Web Vitals observers.', { error: e.message });
    }
  }

  /**
   * Get Core Web Vitals and other key performance metrics.
   * Retrieves metrics collected by observers or from the Performance API.
   * @returns {object} An object containing the collected metrics.
   */
  getCoreWebVitals() {
    try {
      const navEntry = window.performance.getEntriesByType('navigation')[0];
      const paintEntries = window.performance.getEntriesByType('paint');

      const fcpEntry = paintEntries.find(e => e.name === 'first-contentful-paint');
      const ttfb = navEntry ? navEntry.responseStart - navEntry.requestStart : null;

      return {
        ttfb: ttfb,
        fcp: fcpEntry ? fcpEntry.startTime : null,
        lcp: this.lcp,
        cls: this.cls,
      };
    } catch (e) {
      return { error: 'Failed to retrieve Web Vitals.' };
    }
  }

  /**
   * Dynamically injects and runs an axe-core accessibility audit.
   * @returns {Promise<object|null>} A promise that resolves with the axe audit results.
   */
  async runAxeAudit() {
    // Check if axe is already available
    if (typeof window.axe === 'undefined') {
      // If not, and we are not already loading it, inject it
      if (!this.axeLoadingPromise) {
        this.axeLoadingPromise = new Promise((resolve, reject) => {
          const script = document.createElement('script');
          script.src = 'https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.8.4/axe.min.js';
          script.onload = () => {
            this.log('info', 'accessibility', 'axe-core loaded successfully.');
            resolve();
          };
          script.onerror = () => {
            this.log('error', 'accessibility', 'Failed to load axe-core script.');
            this.axeLoadingPromise = null; // Allow retry
            reject(new Error('Failed to load axe-core.'));
          };
          document.head.appendChild(script);
        });
      }
      await this.axeLoadingPromise;
    }

    try {
      // Configure axe to run on the entire document
      const results = await window.axe.run(document.body);
      this.log('metric', 'accessibility', 'Accessibility audit completed.', {
        violations: results.violations.length,
        passes: results.passes.length,
        incomplete: results.incomplete.length,
        results, // Store full results
      });
      return results;
    } catch (error) {
      this.log('error', 'accessibility', 'Error running axe audit.', { error: error.message });
      return null;
    }
  }

  /**
   * Captures a comprehensive snapshot including DOM, accessibility, and performance data.
   * @returns {Promise<void>}
   */
  async captureAccessibilitySnapshot() {
    const domSnapshot = await this.captureDOMSnapshot();
    const accessibility = await this.runAxeAudit();
    const performance = this.getCoreWebVitals();

    this.log('metric', 'accessibilitySnapshot', 'Full accessibility snapshot captured.', {
      snapshot: domSnapshot,
      accessibility,
      performance,
    });

    return { snapshot: domSnapshot, accessibility, performance };
  }

  /**
   * Save logs to sessionStorage
   */
  saveToStorage() {
    try {
      const data = {
        sessionId: this.sessionId,
        entries: this.entries,
        savedAt: Date.now(),
      };
      sessionStorage.setItem(this.storageKey, JSON.stringify(data));
    } catch (e) {
      // Storage might be full or unavailable
    }
  }

  /**
   * Load logs from sessionStorage
   */
  loadFromStorage() {
    try {
      const data = sessionStorage.getItem(this.storageKey);
      if (data) {
        const parsed = JSON.parse(data);
        this.entries = parsed.entries || [];
      }
    } catch (e) {
      // Storage might be unavailable
    }
  }

  /**
   * Get all logs
   */
  getLogs(options = {}) {
    let entries = [...this.entries];

    // Filter by level
    if (options.level) {
      entries = entries.filter(e => e.level === options.level);
    }

    // Filter by category
    if (options.category) {
      entries = entries.filter(e => e.category === options.category);
    }

    // Filter by time range
    if (options.minTime) {
      entries = entries.filter(e => e.timestamp >= options.minTime);
    }

    if (options.maxTime) {
      entries = entries.filter(e => e.timestamp <= options.maxTime);
    }

    // Search in message
    if (options.search) {
      const searchLower = options.search.toLowerCase();
      entries = entries.filter(e =>
        e.message.toLowerCase().includes(searchLower) ||
        JSON.stringify(e.data).toLowerCase().includes(searchLower)
      );
    }

    // Limit results
    const limit = options.limit || 100;
    if (options.reverse) {
      entries.reverse();
    }

    return entries.slice(-limit);
  }

  /**
   * Get errors only
   */
  getErrors() {
    return this.getLogs({ level: 'error', limit: 50, reverse: true });
  }

  /**
   * Get network requests
   */
  getNetworkRequests() {
    return this.getLogs({ category: 'fetch', limit: 100, reverse: true });
  }

  /**
   * Get metrics
   */
  getMetrics() {
    return this.getLogs({ category: 'metric', limit: 100, reverse: true });
  }

  /**
   * Get diagnostic summary
   */
  getDiagnostic() {
    return {
      sessionId: this.sessionId,
      uptime: Date.now() - this.startTime,
      totalLogs: this.entries.length,
      errorCount: this.entries.filter(e => e.level === 'error').length,
      warnCount: this.entries.filter(e => e.level === 'warn').length,
      networkRequests: this.entries.filter(e => e.category === 'fetch').length,
      memory: performance.memory ? {
        usedJSHeapSize: performance.memory.usedJSHeapSize,
        jsHeapSizeLimit: performance.memory.jsHeapSizeLimit,
        usagePercent: ((performance.memory.usedJSHeapSize / performance.memory.jsHeapSizeLimit) * 100).toFixed(2),
      } : null,
      url: window.location.href,
      userAgent: navigator.userAgent,
      recentErrors: this.getErrors().slice(0, 5),
      recentNetworkRequests: this.getNetworkRequests().slice(0, 5),
    };
  }

  /**
   * Export logs as JSON
   */
  exportJSON() {
    return {
      sessionId: this.sessionId,
      exportedAt: new Date().toISOString(),
      logs: this.entries,
      diagnostic: this.getDiagnostic(),
    };
  }

  /**
   * Print formatted logs to console
   */
  printFormatted(options = {}) {
    const logs = this.getLogs(options);

    console.group(`📋 Browser Logs (${logs.length} entries)`);
    console.table(logs.map(e => ({
      Time: new Date(e.timestamp).toLocaleTimeString(),
      Level: e.level.toUpperCase(),
      Category: e.category,
      Message: e.message,
    })));
    console.groupEnd();
  }

  /**
   * Clear logs
   */
  clear() {
    this.entries = [];
    this.lastSyncedIndex = 0;
    this.saveToStorage();
  }

  /**
   * Start auto-sync to server
   */
  startAutoSync() {
    // Sync immediately on startup (after a delay to let the page load)
    setTimeout(() => this.syncToServer(), 5000);

    // Then sync every 30 seconds
    this.syncTimer = setInterval(() => this.syncToServer(), this.autoSyncInterval);

    // Sync before page unload
    window.addEventListener('beforeunload', () => this.syncToServer());
  }

  /**
   * Sync logs to server
   */
  async syncToServer() {
    // Only sync if there are new logs
    if (this.lastSyncedIndex >= this.entries.length) {
      return;
    }

    try {
      const data = this.exportJSON();
      const response = await fetch(this.apiEndpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      });

      if (response.ok) {
        this.lastSyncedIndex = this.entries.length;
        console.debug(`[BrowserLogger] Synced ${this.entries.length} logs to server`);
      } else {
        console.warn(`[BrowserLogger] Failed to sync logs: ${response.statusText}`);
      }
    } catch (error) {
      console.warn('[BrowserLogger] Failed to sync logs:', error.message);
    }
  }

  /**
   * Stop auto-sync
   */
  stopAutoSync() {
    if (this.syncTimer) {
      clearInterval(this.syncTimer);
      this.syncTimer = null;
    }
  }

  /**
   * Capture DOM Snapshot (Shadow State)
   * Returns the current state of the DOM and viewport for remote debugging.
   * Can optionally include accessibility and performance data.
   * @param {object} [options={}] - Options for the snapshot.
   * @param {boolean} [options.includeAccessibility=false] - Whether to run an axe audit.
   * @param {boolean} [options.includePerformance=false] - Whether to include Core Web Vitals.
   * @returns {Promise<object>} A promise that resolves with the snapshot data.
   */
  async captureDOMSnapshot(options = {}) {
    const snapshot = {
      timestamp: Date.now(),
      url: window.location.href,
      html: document.documentElement.outerHTML,
      viewport: {
        width: window.innerWidth,
        height: window.innerHeight,
        devicePixelRatio: window.devicePixelRatio,
      },
      title: document.title,
    };

    if (options.includeAccessibility) {
      snapshot.accessibility = await this.runAxeAudit();
    }

    if (options.includePerformance) {
      snapshot.performance = this.getCoreWebVitals();
    }

    return snapshot;
  }

  /**
   * Setup Shadow State Capture
   * Monitors navigation and errors to create state checkpoints.
   */
  setupSnapshotCapture() {
    // Helper to capture state and log it.
    const handleSnapshot = async (trigger, details) => {
      try {
        const snapshot = await this.captureDOMSnapshot();
        this.log(details.level || 'info', 'snapshot', `State Capture (${trigger})`, {
          trigger,
          details,
          snapshot,
        });

        // If it was a critical error, attempt to flush logs immediately.
        if (details.level === 'error') {
          this.flushViaBeacon();
        }
      } catch (e) {
        this.log('error', 'snapshot', 'Failed to capture snapshot.', { error: e.message });
      }
    };

    // 1. Capture on Navigation (Periodic check for SPA support)
    setInterval(async () => {
      const currentUrl = window.location.href;
      if (currentUrl !== this.lastUrl) {
        const previousUrl = this.lastUrl;
        this.lastUrl = currentUrl;
        await handleSnapshot('navigation', { from: previousUrl, to: currentUrl });
      }
    }, 1000);

    // 2. Capture on Critical Errors
    window.addEventListener('error', (event) => {
      handleSnapshot('uncaughtError', {
        level: 'error',
        error: {
          message: event.message,
          filename: event.filename,
          lineno: event.lineno,
        },
      });
    });

    window.addEventListener('unhandledrejection', (event) => {
      handleSnapshot('unhandledRejection', {
        level: 'error',
        error: {
          reason: event.reason?.message || String(event.reason),
        },
      });
    });
  }

  /**
   * Flush logs via Beacon API
   * Used for critical events where fetch might be cancelled (e.g. page unload/crash)
   */
  flushViaBeacon() {
    if (!navigator.sendBeacon) return;

    // Save current state first
    this.saveToStorage();

    // Prepare payload
    const data = this.exportJSON();

    // Create Blob for proper Content-Type
    const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });

    // Send beacon
    const success = navigator.sendBeacon(this.apiEndpoint, blob);

    if (success) {
      this.lastSyncedIndex = this.entries.length;
      console.debug('[BrowserLogger] Critical logs flushed via Beacon');
    }
  }
}

// Create global instance
const dssLogger = new BrowserLogger();

// Expose to window ONLY in development mode
// This is for debugging purposes only. Production should not expose this.
if (typeof window !== 'undefined' && (
  (typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'development') ||
  window.location.hostname === 'localhost' ||
  window.location.hostname === '127.0.0.1'
)) {
  // Only expose debugging interface with warning
  window.__DSS_BROWSER_LOGS = {
    all: () => dssLogger.getLogs({ limit: 1000 }),
    errors: () => dssLogger.getErrors(),
    network: () => dssLogger.getNetworkRequests(),
    metrics: () => dssLogger.getMetrics(),
    diagnostic: () => dssLogger.getDiagnostic(),
    export: () => dssLogger.exportJSON(),
    print: (options) => dssLogger.printFormatted(options),
    clear: () => dssLogger.clear(),

    // Accessibility and performance auditing
    audit: () => dssLogger.captureAccessibilitySnapshot(),
    vitals: () => dssLogger.getCoreWebVitals(),
    axe: () => dssLogger.runAxeAudit(),

    // Auto-sync controls
    sync: () => dssLogger.syncToServer(),
    stopSync: () => dssLogger.stopAutoSync(),
    startSync: () => dssLogger.startAutoSync(),

    // Quick helpers
    help: () => {
      console.log('%c📋 DSS Browser Logger Commands', 'font-weight: bold; font-size: 14px; color: #4CAF50');
      console.log('%c  __DSS_BROWSER_LOGS.errors()', 'color: #FF5252', '- Show all errors');
      console.log('%c  __DSS_BROWSER_LOGS.diagnostic()', 'color: #2196F3', '- System diagnostic');
      console.log('%c  __DSS_BROWSER_LOGS.all()', 'color: #666', '- All captured logs');
      console.log('%c  __DSS_BROWSER_LOGS.network()', 'color: #9C27B0', '- Network requests');
      console.log('%c  __DSS_BROWSER_LOGS.print()', 'color: #FF9800', '- Print formatted table');
      console.log('%c  __DSS_BROWSER_LOGS.audit()', 'color: #673AB7', '- Run full accessibility audit');
      console.log('%c  __DSS_BROWSER_LOGS.vitals()', 'color: #009688', '- Get Core Web Vitals (LCP, CLS, FCP, TTFB)');
      console.log('%c  __DSS_BROWSER_LOGS.axe()', 'color: #E91E63', '- Run axe-core accessibility scan');
      console.log('%c  __DSS_BROWSER_LOGS.export()', 'color: #00BCD4', '- Export all data (copy this!)');
      console.log('%c  __DSS_BROWSER_LOGS.clear()', 'color: #F44336', '- Clear all logs');
      console.log('%c  __DSS_BROWSER_LOGS.share()', 'color: #4CAF50', '- Generate shareable JSON');
      console.log('%c  __DSS_BROWSER_LOGS.sync()', 'color: #2196F3', '- Sync logs to server now');
      console.log('%c  __DSS_BROWSER_LOGS.stopSync()', 'color: #FF9800', '- Stop auto-sync');
      console.log('%c  __DSS_BROWSER_LOGS.startSync()', 'color: #4CAF50', '- Start auto-sync (30s)');
    },

    // Generate shareable JSON for debugging with Claude
    share: () => {
      const data = dssLogger.exportJSON();
      const json = JSON.stringify(data, null, 2);
      console.log('%c📤 Copy this and share with Claude:', 'font-weight: bold; color: #4CAF50');
      console.log(json);
      return data;
    }
  };

  console.info('%c🔍 DSS Browser Logger Active', 'color: #4CAF50; font-weight: bold;');
  console.info('%c📡 Auto-sync enabled - logs sent to server every 30s', 'color: #2196F3; font-style: italic;');
  console.info('%cType: %c__DSS_BROWSER_LOGS.help()%c for commands', 'color: #666', 'color: #2196F3; font-family: monospace', 'color: #666');
}

export default dssLogger;
