import pc from 'picocolors';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { logger, repomixLogLevels } from '../../src/shared/logger.js';

vi.mock('picocolors', () => ({
  default: {
    red: vi.fn((str) => `RED:${str}`),
    yellow: vi.fn((str) => `YELLOW:${str}`),
    green: vi.fn((str) => `GREEN:${str}`),
    cyan: vi.fn((str) => `CYAN:${str}`),
    dim: vi.fn((str) => `DIM:${str}`),
    blue: vi.fn((str) => `BLUE:${str}`),
    gray: vi.fn((str) => `GRAY:${str}`),
  },
}));

describe('logger', () => {
  beforeEach(() => {
    vi.spyOn(console, 'error').mockImplementation(vi.fn());
    vi.spyOn(console, 'log').mockImplementation(vi.fn());
    logger.init();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('log levels', () => {
    it('should not log anything in SILENT mode', () => {
      logger.setLogLevel(repomixLogLevels.SILENT);

      logger.error('Error message');
      logger.warn('Warning message');
      logger.success('Success message');
      logger.info('Info message');
      logger.note('Note message');
      logger.debug('Debug message');
      logger.trace('Trace message');
      logger.log('Log message');

      expect(console.error).not.toHaveBeenCalled();
      expect(console.log).not.toHaveBeenCalled();
    });

    it('should only log errors in ERROR mode', () => {
      logger.setLogLevel(repomixLogLevels.ERROR);

      logger.error('Error message');
      logger.warn('Warning message');

      expect(console.error).toHaveBeenCalledWith('RED:Error message');
      expect(console.log).not.toHaveBeenCalled();
    });
  });

  it('should log error messages', () => {
    logger.error('Error message');
    expect(console.error).toHaveBeenCalledWith('RED:Error message');
  });

  it('should log warning messages', () => {
    logger.warn('Warning message');
    expect(console.log).toHaveBeenCalledWith('YELLOW:Warning message');
  });

  it('should log success messages', () => {
    logger.success('Success message');
    expect(console.log).toHaveBeenCalledWith('GREEN:Success message');
  });

  it('should log info messages', () => {
    logger.info('Info message');
    expect(console.log).toHaveBeenCalledWith('CYAN:Info message');
  });

  it('should log log messages', () => {
    logger.log('Note message');
    expect(console.log).toHaveBeenCalledWith('Note message');
  });

  it('should not log debug messages when verbose is false', () => {
    logger.debug('Debug message');
    expect(console.log).not.toHaveBeenCalled();
  });

  it('should log debug messages when verbose is true', () => {
    logger.setLogLevel(repomixLogLevels.DEBUG);
    logger.debug('Debug message');
    expect(console.log).toHaveBeenCalledWith('BLUE:Debug message');
  });

  it('should not log trace messages when verbose is false', () => {
    logger.trace('Trace message');
    expect(console.log).not.toHaveBeenCalled();
  });

  it('should log trace messages when verbose is true', () => {
    logger.setLogLevel(repomixLogLevels.DEBUG);
    logger.trace('Trace message');
    expect(console.log).toHaveBeenCalledWith(pc.gray('Trace message'));
  });

  it('should format object arguments correctly', () => {
    const obj = { key: 'value' };
    logger.info('Object:', obj);
    expect(console.log).toHaveBeenCalledWith(expect.stringContaining('CYAN:Object: '));
  });

  it('should handle multiple arguments', () => {
    logger.info('Multiple', 'arguments', 123);
    expect(console.log).toHaveBeenCalledWith('CYAN:Multiple arguments 123');
  });
});

describe('setLogLevelByWorkerData', () => {
  // setLogLevelByWorkerData reads `workerData` (captured at module load) and
  // process.env at call time. Re-import per case via vi.resetModules so each
  // test sees a fresh module with its own mocked workerData.
  const originalLevel = logger.getLogLevel();
  const originalEnvLogLevel = process.env.REPOMIX_LOG_LEVEL;

  beforeEach(() => {
    delete process.env.REPOMIX_LOG_LEVEL;
  });

  afterEach(() => {
    vi.doUnmock('node:worker_threads');
    vi.resetModules();
    logger.setLogLevel(originalLevel);
    if (originalEnvLogLevel === undefined) {
      delete process.env.REPOMIX_LOG_LEVEL;
    } else {
      process.env.REPOMIX_LOG_LEVEL = originalEnvLogLevel;
    }
  });

  it('sets log level from REPOMIX_LOG_LEVEL env var', async () => {
    process.env.REPOMIX_LOG_LEVEL = String(repomixLogLevels.DEBUG);
    const { setLogLevelByWorkerData, logger: freshLogger } = await import('../../src/shared/logger.js');

    setLogLevelByWorkerData();

    expect(freshLogger.getLogLevel()).toBe(repomixLogLevels.DEBUG);
  });

  it('ignores non-numeric REPOMIX_LOG_LEVEL', async () => {
    process.env.REPOMIX_LOG_LEVEL = 'not-a-number';
    const { setLogLevelByWorkerData, logger: freshLogger } = await import('../../src/shared/logger.js');
    const before = freshLogger.getLogLevel();

    setLogLevelByWorkerData();

    expect(freshLogger.getLogLevel()).toBe(before);
  });

  it('ignores out-of-range REPOMIX_LOG_LEVEL', async () => {
    process.env.REPOMIX_LOG_LEVEL = '999';
    const { setLogLevelByWorkerData, logger: freshLogger } = await import('../../src/shared/logger.js');
    const before = freshLogger.getLogLevel();

    setLogLevelByWorkerData();

    expect(freshLogger.getLogLevel()).toBe(before);
  });

  it('falls back to workerData when env var is unset', async () => {
    vi.resetModules();
    vi.doMock('node:worker_threads', () => ({
      workerData: ['some-task', { logLevel: repomixLogLevels.ERROR }],
    }));
    const { setLogLevelByWorkerData, logger: freshLogger } = await import('../../src/shared/logger.js');

    setLogLevelByWorkerData();

    expect(freshLogger.getLogLevel()).toBe(repomixLogLevels.ERROR);
  });

  it('ignores invalid logLevel in workerData', async () => {
    vi.resetModules();
    vi.doMock('node:worker_threads', () => ({
      workerData: ['some-task', { logLevel: 42 }],
    }));
    const { setLogLevelByWorkerData, logger: freshLogger } = await import('../../src/shared/logger.js');
    const before = freshLogger.getLogLevel();

    setLogLevelByWorkerData();

    expect(freshLogger.getLogLevel()).toBe(before);
  });

  it('does nothing when workerData is null and env var is unset', async () => {
    vi.resetModules();
    vi.doMock('node:worker_threads', () => ({ workerData: null }));
    const { setLogLevelByWorkerData, logger: freshLogger } = await import('../../src/shared/logger.js');
    const before = freshLogger.getLogLevel();

    setLogLevelByWorkerData();

    expect(freshLogger.getLogLevel()).toBe(before);
  });
});
