import { WingLog, LogType } from './winglog';
import * as fs from 'fs';
import * as path from 'path';
import pino from 'pino';

// Mock pino to avoid actual file operations during tests
jest.mock('pino');
jest.mock('fs');
jest.mock('path');

const mockPino = pino as jest.Mocked<typeof pino>;
const mockFs = fs as jest.Mocked<typeof fs>;
const mockPath = path as jest.Mocked<typeof path>;

describe('WingLog', () => {
  let wingLog: WingLog;
  let mockLogger: any;
  let mockStream: any;

  beforeEach(() => {
    // Reset all mocks
    jest.clearAllMocks();

    // Setup mock logger
    mockLogger = {
      debug: jest.fn(),
      info: jest.fn(),
      warn: jest.fn(),
      error: jest.fn(),
    };

    mockStream = {
      write: jest.fn(),
    };

    // Mock pino methods
    (mockPino as any).mockReturnValue(mockLogger);
    (mockPino.destination as any) = jest.fn().mockReturnValue(mockStream);
    (mockPino.transport as any) = jest.fn().mockReturnValue(mockStream);
    (mockPino.multistream as any) = jest.fn().mockReturnValue(mockStream);

    // Mock fs methods
    mockFs.existsSync.mockReturnValue(false);
    mockFs.mkdirSync.mockImplementation(() => undefined);

    // Mock path methods
    mockPath.join.mockImplementation((...args) => args.join('/'));
    mockPath.resolve.mockImplementation((...args) => args.join('/'));

    // Mock process.cwd
    Object.defineProperty(process, 'cwd', {
      value: jest.fn().mockReturnValue('/test/cwd'),
      writable: true,
    });

    wingLog = new WingLog('test-logger');
  });

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

  describe('Constructor', () => {
    it('should create a WingLog instance with the given name', () => {
      expect(wingLog).toBeInstanceOf(WingLog);
    });

    it('should create logs directory if it does not exist', () => {
      expect(mockFs.existsSync).toHaveBeenCalledWith('/test/cwd/logs');
      expect(mockFs.mkdirSync).toHaveBeenCalledWith('/test/cwd/logs', { recursive: true });
    });

    it('should create logs directory even if it already exists (mkdirSync handles this)', () => {
      mockFs.existsSync.mockReturnValue(true);
      new WingLog('test-logger-2');
      expect(mockFs.mkdirSync).toHaveBeenCalledWith('/test/cwd/logs', { recursive: true });
    });

    it('should initialize pino logger with correct configuration', () => {
      expect(mockPino).toHaveBeenCalledWith(
        {
          level: 'debug',
          timestamp: expect.any(Function),
        },
        mockStream
      );
    });

    it('should log initialization message', () => {
      expect(mockLogger.debug).toHaveBeenCalledWith('Logger initialized: [test-logger]');
    });
  });

  describe('Logging Methods', () => {
    beforeEach(() => {
      // Mock Date.now for consistent testing
      jest.spyOn(Date, 'now').mockReturnValue(1000000);
    });

    it('should log started message correctly', () => {
      const result = wingLog.started('Test started');
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Test started');
      expect(result).toBe(0);
    });

    it('should log finished message correctly', () => {
      const result = wingLog.finished('Test finished');
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Test finished');
      expect(result).toBe(0);
    });

    it('should log success message correctly', () => {
      const result = wingLog.success('Test success');
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Test success');
      expect(result).toBe(0);
    });

    it('should log failed message correctly', () => {
      const result = wingLog.failed('Test failed');
      expect(mockLogger.error).toHaveBeenCalledWith('[test-logger]:Test failed');
      expect(result).toBe(0);
    });

    it('should log info message correctly', () => {
      const result = wingLog.info('Test info');
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Test info');
      expect(result).toBe(0);
    });

    it('should log warn message correctly', () => {
      const result = wingLog.warn('Test warning');
      expect(mockLogger.warn).toHaveBeenCalledWith('[test-logger]:Test warning');
      expect(result).toBe(0);
    });

    it('should log debug message correctly', () => {
      const result = wingLog.debug('Test debug');
      expect(mockLogger.debug).toHaveBeenCalledWith('[test-logger]:Test debug');
      expect(result).toBe(0);
    });
  });

  describe('Duration Calculation', () => {
    beforeEach(() => {
      // Mock Date.now to return a fixed timestamp
      jest.spyOn(Date, 'now').mockReturnValue(1000000);
    });

    it('should calculate duration correctly when startTime is provided', () => {
      const startTime = 999000; // 1 second ago
      const result = wingLog.started('Test with duration', startTime);
      
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Duration:[00:01]:Test with duration');
      expect(result).toBe(1);
    });

    it('should handle zero duration correctly', () => {
      const startTime = 1000000; // Same time
      const result = wingLog.started('Test with zero duration', startTime);
      
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Duration:[00:00]:Test with zero duration');
      expect(result).toBe(0);
    });

    it('should handle duration with minutes and seconds', () => {
      const startTime = 940000; // 60 seconds ago
      const result = wingLog.started('Test with minutes', startTime);
      
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Duration:[01:00]:Test with minutes');
      expect(result).toBe(60);
    });

    it('should handle duration with decimal seconds', () => {
      const startTime = 999500; // 0.5 seconds ago
      const result = wingLog.started('Test with decimal duration', startTime);
      
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Duration:[00:01]:Test with decimal duration');
      expect(result).toBe(0.5);
    });

    it('should handle very large duration values', () => {
      const startTime = 1; // Very long time ago (but not 0 to avoid falsy check)
      const result = wingLog.started('Long running task', startTime);
      
      expect(result).toBe(1000); // 999.99 seconds rounded to 1000.00 by toFixed(2)
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Duration:[16:40]:Long running task');
    });
  });

  describe('Error Handling', () => {
    it('should handle Error objects correctly', () => {
      const error = new Error('Test error message');
      wingLog.error('Operation failed', error);
      
      expect(mockLogger.error).toHaveBeenCalledWith('[test-logger]:Operation failed: Test error message {"error":{}}');
    });

    it('should handle non-Error objects correctly', () => {
      const error = 'String error';
      wingLog.error('Operation failed', error);
      
      expect(mockLogger.error).toHaveBeenCalledWith('[test-logger]:Operation failed: String error {"error":{}}');
    });

    it('should handle null/undefined errors', () => {
      wingLog.error('Operation failed', null);
      
      expect(mockLogger.error).toHaveBeenCalledWith('[test-logger]:Operation failed: null {"error":{}}');
    });
  });



  describe('Overloaded Methods', () => {
    beforeEach(() => {
      // Mock Date.now to return a fixed timestamp
      jest.spyOn(Date, 'now').mockReturnValue(1000000);
    });

    it('should handle info with simple message', () => {
      const result = wingLog.info('Simple message');
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Simple message');
      expect(result).toBe(0);
    });

    it('should handle info with structured data', () => {
      const record = { userId: 123, action: 'login' };
      wingLog.info('User action', record);
      
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:User action {"userId":123,"action":"login"}');
    });

    it('should handle debug with simple message', () => {
      const result = wingLog.debug('Simple debug message');
      expect(mockLogger.debug).toHaveBeenCalledWith('[test-logger]:Simple debug message');
      expect(result).toBe(0);
    });

    it('should handle debug with structured data', () => {
      const record = { query: 'SELECT * FROM users', duration: 45 };
      wingLog.debug('Database query', record);
      
      expect(mockLogger.debug).toHaveBeenCalledWith('[test-logger]:Database query {"query":"SELECT * FROM users","duration":45}');
    });

    it('should handle failed with simple message', () => {
      const result = wingLog.failed('Simple error message');
      expect(mockLogger.error).toHaveBeenCalledWith('[test-logger]:Simple error message');
      expect(result).toBe(0);
    });

    it('should handle failed with structured data', () => {
      const record = { userId: 123, reason: 'Invalid token', ip: '192.168.1.1' };
      wingLog.failed('Authentication failed', record);
      
      expect(mockLogger.error).toHaveBeenCalledWith('[test-logger]:Authentication failed {"userId":123,"reason":"Invalid token","ip":"192.168.1.1"}');
    });

    it('should handle warn with simple message', () => {
      const result = wingLog.warn('Simple warning message');
      expect(mockLogger.warn).toHaveBeenCalledWith('[test-logger]:Simple warning message');
      expect(result).toBe(0);
    });

    it('should handle warn with structured data', () => {
      const record = { memoryUsage: '85%', threshold: '80%' };
      wingLog.warn('High memory usage', record);
      
      expect(mockLogger.warn).toHaveBeenCalledWith('[test-logger]:High memory usage {"memoryUsage":"85%","threshold":"80%"}');
    });

    it('should handle success with simple message', () => {
      const result = wingLog.success('Simple success message');
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Simple success message');
      expect(result).toBe(0);
    });

    it('should handle success with structured data', () => {
      const record = { userId: 123, email: 'user@example.com' };
      wingLog.success('User created successfully', record);
      
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:User created successfully {"userId":123,"email":"user@example.com"}');
    });

    it('should handle started with simple message', () => {
      const result = wingLog.started('Simple start message');
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Simple start message');
      expect(result).toBe(0);
    });

    it('should handle started with structured data', () => {
      const record = { jobId: 'job-123', priority: 'high' };
      wingLog.started('Job processing started', record);
      
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Job processing started {"jobId":"job-123","priority":"high"}');
    });

    it('should handle finished with simple message', () => {
      const result = wingLog.finished('Simple finish message');
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Simple finish message');
      expect(result).toBe(0);
    });

    it('should handle finished with structured data', () => {
      const record = { jobId: 'job-123', duration: 45, status: 'completed' };
      wingLog.finished('Job processing finished', record);
      
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Job processing finished {"jobId":"job-123","duration":45,"status":"completed"}');
    });

    it('should handle duration calculation with simple message', () => {
      const startTime = 999000; // 1 second ago
      const result = wingLog.info('Message with duration', startTime);
      
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Duration:[00:01]:Message with duration');
      expect(result).toBe(1);
    });

    it('should handle duration calculation with structured data', () => {
      const record = { userId: 123, action: 'login' };
      wingLog.info('User action with duration', record);
      
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:User action with duration {"userId":123,"action":"login"}');
    });
  });

  describe('LogType Enum', () => {
    it('should have all expected log types', () => {
      expect(LogType.FAILED).toBe('FAILED');
      expect(LogType.WARN).toBe('WARN');
      expect(LogType.DEBUG).toBe('DEBUG');
      expect(LogType.SUCCESS).toBe('SUCCESS');
      expect(LogType.STARTED).toBe('STARTED');
      expect(LogType.FINISHED).toBe('FINISHED');
      expect(LogType.INFO).toBe('INFO');
    });
  });

  describe('Edge Cases', () => {
    it('should handle empty messages', () => {
      wingLog.info('');
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:');
    });

    it('should handle messages with special characters', () => {
      wingLog.info('Message with "quotes" and \'apostrophes\'');
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Message with "quotes" and \'apostrophes\'');
    });

    it('should handle pino transport fallback', () => {
      // Mock pino.transport to throw an error
      (mockPino.transport as any).mockImplementation(() => {
        throw new Error('Transport not available');
      });

      // Should not throw when creating new instance
      expect(() => new WingLog('fallback-test')).not.toThrow();
    });
  });

  describe('Integration Tests', () => {
    it('should work with multiple log calls', () => {
      wingLog.started('Task started');
      wingLog.info('Task in progress');
      wingLog.success('Task completed');

      expect(mockLogger.info).toHaveBeenCalledTimes(3);
      expect(mockLogger.info).toHaveBeenNthCalledWith(1, '[test-logger]:Task started');
      expect(mockLogger.info).toHaveBeenNthCalledWith(2, '[test-logger]:Task in progress');
      expect(mockLogger.info).toHaveBeenNthCalledWith(3, '[test-logger]:Task completed');
    });

    it('should handle mixed log levels', () => {
      wingLog.debug('Debug message');
      wingLog.info('Info message');
      wingLog.warn('Warning message');
      wingLog.error('Error message', new Error('Test error'));

      expect(mockLogger.debug).toHaveBeenCalledWith('[test-logger]:Debug message');
      expect(mockLogger.info).toHaveBeenCalledWith('[test-logger]:Info message');
      expect(mockLogger.warn).toHaveBeenCalledWith('[test-logger]:Warning message');
      expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining('[test-logger]:Error message'));
    });
  });
});
