import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { createLogger, LogLevel, getLogLevel, setLogLevel } from '../src/logger';

describe('logger.ts', () => {
  let originalConsoleError: typeof console.error;
  let originalConsoleWarn: typeof console.warn;
  let originalConsoleLog: typeof console.log;
  let originalLogLevel: LogLevel;

  beforeEach(() => {
    // Save original console methods and log level
    originalConsoleError = console.error;
    originalConsoleWarn = console.warn;
    originalConsoleLog = console.log;
    originalLogLevel = getLogLevel();

    // Mock console methods
    console.error = vi.fn();
    console.warn = vi.fn();
    console.log = vi.fn();
  });

  afterEach(() => {
    // Restore original console methods and log level
    console.error = originalConsoleError;
    console.warn = originalConsoleWarn;
    console.log = originalConsoleLog;
    setLogLevel(originalLogLevel);
  });

  describe('createLogger', () => {
    it('should create logger with context prefix', () => {
      const logger = createLogger('TestContext');
      setLogLevel(LogLevel.INFO);

      logger.info('test message');

      expect(console.log).toHaveBeenCalledWith('[TestContext]', 'test message');
    });

    it('should handle multiple arguments', () => {
      const logger = createLogger('Test');
      setLogLevel(LogLevel.INFO);

      logger.info('message', { data: 123 }, [1, 2, 3]);

      expect(console.log).toHaveBeenCalledWith('[Test]', 'message', { data: 123 }, [1, 2, 3]);
    });
  });

  describe('Log levels', () => {
    it('should log error at ERROR level', () => {
      const logger = createLogger('Test');
      setLogLevel(LogLevel.ERROR);

      logger.error('error message');
      logger.warn('warn message');
      logger.info('info message');
      logger.debug('debug message');

      expect(console.error).toHaveBeenCalledTimes(1);
      expect(console.warn).not.toHaveBeenCalled();
      expect(console.log).not.toHaveBeenCalled();
    });

    it('should log error and warn at WARN level', () => {
      const logger = createLogger('Test');
      setLogLevel(LogLevel.WARN);

      logger.error('error message');
      logger.warn('warn message');
      logger.info('info message');
      logger.debug('debug message');

      expect(console.error).toHaveBeenCalledTimes(1);
      expect(console.warn).toHaveBeenCalledTimes(1);
      expect(console.log).not.toHaveBeenCalled();
    });

    it('should log error, warn, and info at INFO level', () => {
      const logger = createLogger('Test');
      setLogLevel(LogLevel.INFO);

      logger.error('error message');
      logger.warn('warn message');
      logger.info('info message');
      logger.debug('debug message');

      expect(console.error).toHaveBeenCalledTimes(1);
      expect(console.warn).toHaveBeenCalledTimes(1);
      expect(console.log).toHaveBeenCalledTimes(1); // Only info, not debug
    });

    it('should log all messages at DEBUG level', () => {
      const logger = createLogger('Test');
      setLogLevel(LogLevel.DEBUG);

      logger.error('error message');
      logger.warn('warn message');
      logger.info('info message');
      logger.debug('debug message');

      expect(console.error).toHaveBeenCalledTimes(1);
      expect(console.warn).toHaveBeenCalledTimes(1);
      expect(console.log).toHaveBeenCalledTimes(2); // info + debug
    });
  });

  describe('error method', () => {
    it('should use console.error', () => {
      const logger = createLogger('ErrorTest');
      setLogLevel(LogLevel.ERROR);

      logger.error('error occurred', { code: 500 });

      expect(console.error).toHaveBeenCalledWith('[ErrorTest]', 'error occurred', { code: 500 });
    });

    it('should not log when level is below ERROR', () => {
      const logger = createLogger('Test');
      setLogLevel((LogLevel.ERROR - 1) as LogLevel);

      logger.error('should not appear');

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

  describe('warn method', () => {
    it('should use console.warn', () => {
      const logger = createLogger('WarnTest');
      setLogLevel(LogLevel.WARN);

      logger.warn('warning message');

      expect(console.warn).toHaveBeenCalledWith('[WarnTest]', 'warning message');
    });

    it('should not log when level is below WARN', () => {
      const logger = createLogger('Test');
      setLogLevel(LogLevel.ERROR);

      logger.warn('should not appear');

      expect(console.warn).not.toHaveBeenCalled();
    });
  });

  describe('info method', () => {
    it('should use console.log', () => {
      const logger = createLogger('InfoTest');
      setLogLevel(LogLevel.INFO);

      logger.info('info message');

      expect(console.log).toHaveBeenCalledWith('[InfoTest]', 'info message');
    });

    it('should not log when level is below INFO', () => {
      const logger = createLogger('Test');
      setLogLevel(LogLevel.WARN);

      logger.info('should not appear');

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

  describe('debug method', () => {
    it('should use console.log with DEBUG prefix', () => {
      const logger = createLogger('DebugTest');
      setLogLevel(LogLevel.DEBUG);

      logger.debug('debug details', { trace: '...' });

      expect(console.log).toHaveBeenCalledWith('[DebugTest]', '[DEBUG]', 'debug details', {
        trace: '...',
      });
    });

    it('should not log when level is below DEBUG', () => {
      const logger = createLogger('Test');
      setLogLevel(LogLevel.INFO);

      logger.debug('should not appear');

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

  describe('getLogLevel', () => {
    it('should return current log level', () => {
      setLogLevel(LogLevel.DEBUG);
      expect(getLogLevel()).toBe(LogLevel.DEBUG);

      setLogLevel(LogLevel.ERROR);
      expect(getLogLevel()).toBe(LogLevel.ERROR);
    });
  });

  describe('setLogLevel', () => {
    it('should change log level', () => {
      setLogLevel(LogLevel.WARN);
      expect(getLogLevel()).toBe(LogLevel.WARN);

      setLogLevel(LogLevel.DEBUG);
      expect(getLogLevel()).toBe(LogLevel.DEBUG);
    });

    it('should affect subsequent logging', () => {
      const logger = createLogger('Test');

      setLogLevel(LogLevel.ERROR);
      logger.info('should not log');
      expect(console.log).not.toHaveBeenCalled();

      setLogLevel(LogLevel.INFO);
      logger.info('should log');
      expect(console.log).toHaveBeenCalledWith('[Test]', 'should log');
    });
  });

  describe('Multiple logger instances', () => {
    it('should maintain separate contexts', () => {
      const logger1 = createLogger('Service1');
      const logger2 = createLogger('Service2');
      setLogLevel(LogLevel.INFO);

      logger1.info('from service 1');
      logger2.info('from service 2');

      expect(console.log).toHaveBeenNthCalledWith(1, '[Service1]', 'from service 1');
      expect(console.log).toHaveBeenNthCalledWith(2, '[Service2]', 'from service 2');
    });

    it('should share log level setting', () => {
      const logger1 = createLogger('Service1');
      const logger2 = createLogger('Service2');

      setLogLevel(LogLevel.ERROR);

      logger1.info('should not log');
      logger2.info('should not log');

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

  describe('Edge cases', () => {
    it('should handle empty context string', () => {
      const logger = createLogger('');
      setLogLevel(LogLevel.INFO);

      logger.info('message');

      expect(console.log).toHaveBeenCalledWith('[]', 'message');
    });

    it('should handle context with special characters', () => {
      const logger = createLogger('Test-Service_v2.0');
      setLogLevel(LogLevel.INFO);

      logger.info('message');

      expect(console.log).toHaveBeenCalledWith('[Test-Service_v2.0]', 'message');
    });

    it('should handle no arguments to log methods', () => {
      const logger = createLogger('Test');
      setLogLevel(LogLevel.INFO);

      logger.info();

      expect(console.log).toHaveBeenCalledWith('[Test]');
    });

    it('should handle complex object arguments', () => {
      const logger = createLogger('Test');
      setLogLevel(LogLevel.INFO);

      const complexObj = {
        nested: { deep: { value: 123 } },
        array: [1, 2, 3],
        fn: () => {},
      };

      logger.info('complex', complexObj);

      expect(console.log).toHaveBeenCalledWith('[Test]', 'complex', complexObj);
    });
  });
});
