import { Injectable, LoggerService } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

type AppLogLevel = 'error' | 'warn' | 'log' | 'debug' | 'verbose';

@Injectable()
export class AppLoggerService implements LoggerService {
  private readonly levelPriority: Record<AppLogLevel, number> = {
    error: 0,
    warn: 1,
    log: 2,
    debug: 3,
    verbose: 4,
  };

  constructor(private readonly configService: ConfigService) {}

  log(message: any, context?: string): void {
    this.write('log', message, undefined, context);
  }

  error(message: any, trace?: string, context?: string): void {
    this.write('error', message, trace, context);
  }

  warn(message: any, context?: string): void {
    this.write('warn', message, undefined, context);
  }

  debug(message: any, context?: string): void {
    this.write('debug', message, undefined, context);
  }

  verbose(message: any, context?: string): void {
    this.write('verbose', message, undefined, context);
  }

  logHttp(payload: Record<string, unknown>): void {
    this.write('log', 'http_request', undefined, 'HttpLogger', payload);
  }

  private write(
    level: AppLogLevel,
    message: any,
    trace?: string,
    context?: string,
    extra: Record<string, unknown> = {},
  ): void {
    if (!this.shouldLog(level)) {
      return;
    }

    const entry: Record<string, unknown> = {
      level,
      timestamp: new Date().toISOString(),
      context: context ?? 'Application',
      ...extra,
    };

    if (typeof message === 'string') {
      entry.message = message;
    } else if (message instanceof Error) {
      entry.message = message.message;
      entry.errorName = message.name;
      entry.stack = message.stack;
    } else {
      entry.message = 'structured_log';
      entry.payload = message;
    }

    if (trace) {
      entry.trace = trace;
    }

    const serialized = `${JSON.stringify(entry)}\n`;
    if (level === 'error') {
      process.stderr.write(serialized);
      return;
    }

    process.stdout.write(serialized);
  }

  private shouldLog(level: AppLogLevel): boolean {
    const configuredLevel =
      (this.configService.get<string>('logging.level', { infer: true }) as AppLogLevel | undefined) ??
      'log';

    return this.levelPriority[level] <= this.levelPriority[configuredLevel];
  }
}
