/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { vi, beforeEach, afterEach } from 'vitest';
import { format } from 'node:util';
import { coreEvents, debugLogger } from '@google/gemini-cli-core';
import { themeManager } from './src/ui/themes/theme-manager.js';
import { mockInkSpinner } from './src/test-utils/mockSpinner.js';

// Globally mock ink-spinner to prevent non-deterministic snapshot/act flakes.
mockInkSpinner();

// Unset CI environment variable so that ink renders dynamically as it does in a real terminal
if (process.env.CI !== undefined) {
  delete process.env.CI;
}

global.IS_REACT_ACT_ENVIRONMENT = true;

// Increase max listeners to avoid warnings in large test suites
coreEvents.setMaxListeners(100);

// Unset NO_COLOR environment variable to ensure consistent theme behavior between local and CI test runs
if (process.env.NO_COLOR !== undefined) {
  delete process.env.NO_COLOR;
}

// Force true color output for ink so that snapshots always include color information.
process.env.FORCE_COLOR = '3';

// Force generic keybinding hints to ensure stable snapshots across different operating systems.
process.env.FORCE_GENERIC_KEYBINDING_HINTS = 'true';

// Force generic terminal declaration to ensure stable snapshots across different host environments.
process.env.TERM_PROGRAM = 'generic';

// Force true color support for terminal capability checks to ensure stable snapshots across different terminals.
process.env.COLORTERM = 'truecolor';

// Mock stdout color depth to ensure true color capability is detected consistently across local and headless CI runners.
if (process.stdout) {
  process.stdout.getColorDepth = () => 24;
}

import './src/test-utils/customMatchers.js';

let consoleErrorSpy: vi.SpyInstance;
let actWarnings: Array<{ message: string; stack: string }> = [];

let logSpy: vi.SpyInstance;
let warnSpy: vi.SpyInstance;
let errorSpy: vi.SpyInstance;
let debugSpy: vi.SpyInstance;

beforeEach(() => {
  // Reset themeManager state to ensure test isolation
  themeManager.resetForTesting();

  // Mock debugLogger to avoid test output noise
  logSpy = vi.spyOn(debugLogger, 'log').mockImplementation(() => {});
  warnSpy = vi.spyOn(debugLogger, 'warn').mockImplementation((...args) => {
    console.warn(...args);
  });
  errorSpy = vi.spyOn(debugLogger, 'error').mockImplementation((...args) => {
    console.error(...args);
  });
  debugSpy = vi.spyOn(debugLogger, 'debug').mockImplementation(() => {});

  actWarnings = [];
  consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => {
    const firstArg = args[0];
    if (
      typeof firstArg === 'string' &&
      firstArg.includes('was not wrapped in act(...)')
    ) {
      const stackLines = (new Error().stack || '').split('\n');
      let lastReactFrameIndex = -1;

      // Find the index of the last frame that comes from react-reconciler
      for (let i = 0; i < stackLines.length; i++) {
        if (stackLines[i].includes('react-reconciler')) {
          lastReactFrameIndex = i;
        }
      }

      // If we found react-reconciler frames, start the stack trace after the last one.
      // Otherwise, just strip the first line (which is the Error message itself).
      const relevantStack =
        lastReactFrameIndex !== -1
          ? stackLines.slice(lastReactFrameIndex + 1).join('\n')
          : stackLines.slice(1).join('\n');

      if (
        relevantStack.includes('OverflowContext.tsx') ||
        relevantStack.includes('useTimedMessage.ts') ||
        relevantStack.includes('useInlineEditBuffer.ts')
      ) {
        return;
      }

      actWarnings.push({
        message: format(...args),
        stack: relevantStack,
      });
    }
  });
});

afterEach(() => {
  consoleErrorSpy.mockRestore();

  logSpy?.mockRestore();
  warnSpy?.mockRestore();
  errorSpy?.mockRestore();
  debugSpy?.mockRestore();

  vi.unstubAllEnvs();
  if (actWarnings.length > 0) {
    const messages = actWarnings
      .map(({ message, stack }) => `${message}\n${stack}`)
      .join('\n\n');
    throw new Error(`Failing test due to "act(...)" warnings:\n${messages}`);
  }
});
