export interface LoggerConfig {
  level: string;
  format: 'json' | 'simple' | 'colored';
  transports: string[];
  file?: {
    enabled: boolean;
    path: string;
    maxSize: string;
    maxFiles: number;
  };
  console?: {
    enabled: boolean;
    colors: boolean;
  };
  database?: {
    enabled: boolean;
    collection: string;
    connection?: any;
  };
  external?: {
    enabled: boolean;
    type: 'serilog' | 'sentry' | 'loggly' | 'papertrail';
    url?: string;
    apiKey?: string;
    appName?: string;
  };
}

export interface LogEntry {
  timestamp: string;
  level: string;
  message: string;
  meta?: any;
  trace?: string;
  userId?: string;
  requestId?: string;
  sessionId?: string;
}

export interface LogTransport {
  name: string;
  log(entry: LogEntry): Promise<void>;
  isEnabled(): boolean;
}

class ConsoleTransport implements LogTransport {
  name = 'console';
  private colors: boolean;
  private format: string;

  constructor(config: LoggerConfig) {
    this.colors = config.console?.colors ?? true;
    this.format = config.format;
  }

  isEnabled(): boolean {
    return true; // Console is always enabled
  }

  async log(entry: LogEntry): Promise<void> {
    const formattedMessage = this.formatMessage(entry);
    
    switch (entry.level) {
      case 'error':
        console.error(formattedMessage);
        break;
      case 'warn':
        console.warn(formattedMessage);
        break;
      case 'info':
        console.log(formattedMessage);
        break;
      case 'debug':
        console.debug(formattedMessage);
        break;
      default:
        console.log(formattedMessage);
    }
  }

  private formatMessage(entry: LogEntry): string {
    if (this.format === 'json') {
      return JSON.stringify(entry);
    }

    if (this.format === 'colored' && this.colors) {
      const colors = {
        error: '\x1b[31m', // Red
        warn: '\x1b[33m',  // Yellow
        info: '\x1b[36m',  // Cyan
        debug: '\x1b[35m', // Magenta
        verbose: '\x1b[90m' // Gray
      };
      const reset = '\x1b[0m';
      return `${colors[entry.level as keyof typeof colors]}[${entry.level.toUpperCase()}]${reset} ${entry.timestamp} - ${entry.message}`;
    }

    return `[${entry.level.toUpperCase()}] ${entry.timestamp} - ${entry.message}`;
  }
}

class FileTransport implements LogTransport {
  name = 'file';
  private enabled: boolean;
  private path: string;
  private fs: any;

  constructor(config: LoggerConfig) {
    this.enabled = config.file?.enabled ?? false;
    this.path = config.file?.path ?? './logs/app.log';
    
    if (this.enabled) {
      // Dynamically import fs-extra to avoid issues
      this.fs = require('fs-extra');
      this.ensureLogDirectory();
    }
  }

  isEnabled(): boolean {
    return this.enabled;
  }

  async log(entry: LogEntry): Promise<void> {
    if (!this.enabled || !this.fs) return;

    try {
      const logLine = JSON.stringify(entry) + '\n';
      await this.fs.appendFile(this.path, logLine);
    } catch (error) {
      console.error('Failed to write to log file:', error);
    }
  }

  private async ensureLogDirectory(): Promise<void> {
    if (!this.fs) return;
    
    try {
      const lastSlashIndex = this.path.lastIndexOf('/');
      if (lastSlashIndex > 0) {
        const dir = this.path.substring(0, lastSlashIndex);
        await this.fs.ensureDir(dir);
      }
    } catch (error) {
      console.error('Failed to create log directory:', error);
    }
  }
}

class DatabaseTransport implements LogTransport {
  name = 'database';
  private enabled: boolean;
  private collection: string;
  private connection: any;

  constructor(config: LoggerConfig) {
    this.enabled = config.database?.enabled ?? false;
    this.collection = config.database?.collection ?? 'logs';
    this.connection = config.database?.connection;
  }

  isEnabled(): boolean {
    return this.enabled && this.connection;
  }

  async log(entry: LogEntry): Promise<void> {
    if (!this.enabled || !this.connection) return;

    try {
      const logDocument = {
        ...entry,
        createdAt: new Date(entry.timestamp)
      };

      await this.connection.collection(this.collection).insertOne(logDocument);
    } catch (error) {
      console.error('Failed to write to database:', error);
    }
  }
}

class ExternalTransport implements LogTransport {
  name = 'external';
  private enabled: boolean;
  private type: string;
  private url?: string;
  private apiKey?: string;
  private appName?: string;

  constructor(config: LoggerConfig) {
    this.enabled = config.external?.enabled ?? false;
    this.type = config.external?.type ?? 'serilog';
    this.url = config.external?.url;
    this.apiKey = config.external?.apiKey;
    this.appName = config.external?.appName;
  }

  isEnabled(): boolean {
    return this.enabled === true && this.url !== undefined;
  }

  async log(entry: LogEntry): Promise<void> {
    if (!this.enabled || !this.url) return;

    try {
      const payload = this.formatPayload(entry);
      
      await fetch(this.url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...(this.apiKey && { 'Authorization': `Bearer ${this.apiKey}` })
        },
        body: JSON.stringify(payload)
      });
    } catch (error) {
      console.error('Failed to send to external logging service:', error);
    }
  }

  private formatPayload(entry: LogEntry): any {
    switch (this.type) {
      case 'serilog':
        return {
          timestamp: entry.timestamp,
          level: entry.level,
          message: entry.message,
          properties: entry.meta,
          ...(this.appName && { application: this.appName })
        };
      
      case 'sentry':
        return {
          level: entry.level,
          message: entry.message,
          timestamp: entry.timestamp,
          extra: entry.meta,
          tags: {
            ...(this.appName && { application: this.appName })
          }
        };
      
      case 'loggly':
        return {
          level: entry.level,
          message: entry.message,
          timestamp: entry.timestamp,
          meta: entry.meta,
          ...(this.appName && { application: this.appName })
        };
      
      default:
        return entry;
    }
  }
}

class Logger {
  private level: string;
  private transports: LogTransport[] = [];
  private requestId?: string;
  private userId?: string;
  private sessionId?: string;

  constructor(config: LoggerConfig) {
    this.level = config.level || 'info';
    
    // Initialize transports
    this.transports.push(new ConsoleTransport(config));
    
    if (config.file?.enabled) {
      this.transports.push(new FileTransport(config));
    }
    
    if (config.database?.enabled) {
      this.transports.push(new DatabaseTransport(config));
    }
    
    if (config.external?.enabled) {
      this.transports.push(new ExternalTransport(config));
    }
  }

  private shouldLog(level: string): boolean {
    const levels = ['error', 'warn', 'info', 'debug', 'verbose'];
    return levels.indexOf(level) <= levels.indexOf(this.level);
  }

  private createLogEntry(level: string, message: string, meta?: any): LogEntry {
    return {
      timestamp: new Date().toISOString(),
      level: level.toUpperCase(),
      message,
      meta,
      ...(this.requestId && { requestId: this.requestId }),
      ...(this.userId && { userId: this.userId }),
      ...(this.sessionId && { sessionId: this.sessionId })
    };
  }

  private async writeToTransports(entry: LogEntry): Promise<void> {
    const enabledTransports = this.transports.filter(t => t.isEnabled());
    
    await Promise.allSettled(
      enabledTransports.map(transport => transport.log(entry))
    );
  }

  info(message: string, meta?: any): void {
    if (this.shouldLog('info')) {
      const entry = this.createLogEntry('info', message, meta);
      this.writeToTransports(entry);
    }
  }

  warn(message: string, meta?: any): void {
    if (this.shouldLog('warn')) {
      const entry = this.createLogEntry('warn', message, meta);
      this.writeToTransports(entry);
    }
  }

  error(message: string, meta?: any): void {
    if (this.shouldLog('error')) {
      const entry = this.createLogEntry('error', message, meta);
      this.writeToTransports(entry);
    }
  }

  debug(message: string, meta?: any): void {
    if (this.shouldLog('debug')) {
      const entry = this.createLogEntry('debug', message, meta);
      this.writeToTransports(entry);
    }
  }

  verbose(message: string, meta?: any): void {
    if (this.shouldLog('verbose')) {
      const entry = this.createLogEntry('verbose', message, meta);
      this.writeToTransports(entry);
    }
  }

  // Method to change log level at runtime
  setLevel(level: string): void {
    this.level = level;
  }

  // Method to get current log level
  getLevel(): string {
    return this.level;
  }

  // Method to set context for request tracking
  setContext(requestId?: string, userId?: string, sessionId?: string): void {
    this.requestId = requestId;
    this.userId = userId;
    this.sessionId = sessionId;
  }

  // Method to add custom transport
  addTransport(transport: LogTransport): void {
    this.transports.push(transport);
  }

  // Method to get all transports
  getTransports(): LogTransport[] {
    return this.transports;
  }
}

export const logger = new Logger({
  level: process.env.LOG_LEVEL || 'info',
  format: (process.env.LOG_FORMAT as 'json' | 'simple' | 'colored') || 'colored',
  transports: ['console'],
  console: {
    enabled: true,
    colors: process.env.LOG_COLORS !== 'false'
  },
  file: {
    enabled: process.env.LOG_FILE_ENABLED === 'true',
    path: process.env.LOG_FILE_PATH || './logs/app.log',
    maxSize: process.env.LOG_FILE_MAX_SIZE || '10m',
    maxFiles: parseInt(process.env.LOG_FILE_MAX_FILES || '5')
  },
  database: {
    enabled: process.env.LOG_DATABASE_ENABLED === 'true',
    collection: process.env.LOG_DATABASE_COLLECTION || 'logs'
  },
  external: {
    enabled: process.env.LOG_EXTERNAL_ENABLED === 'true',
    type: (process.env.LOG_EXTERNAL_TYPE as 'serilog' | 'sentry' | 'loggly' | 'papertrail') || 'serilog',
    url: process.env.LOG_EXTERNAL_URL,
    apiKey: process.env.LOG_EXTERNAL_API_KEY,
    appName: process.env.LOG_EXTERNAL_APP_NAME
  }
}); 