/**
 * @copyright 2026 NHCarrigan
 * @license Naomi's Public License
 * @author Naomi Carrigan
 */

import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from '../../environments/environment';

interface LogPayload {
  level: 'debug' | 'info' | 'warn' | 'error';
  message: string;
  context?: string;
  error?: {
    name: string;
    message: string;
    stack?: string;
  };
}

@Injectable({
  providedIn: 'root'
})
export class ConsoleLoggerService {
  private http = inject(HttpClient);
  private originalConsole = {
    log: console.log.bind(console),
    error: console.error.bind(console),
    warn: console.warn.bind(console),
    debug: console.debug.bind(console),
    info: console.info.bind(console)
  };

  /**
   * Initialises the console override to pipe logs to the API.
   */
  initialise(): void {
    console.log = (...args: unknown[]) => {
      this.originalConsole.log(...args);
      this.sendLog('info', this.formatArgs(args));
    };

    console.info = (...args: unknown[]) => {
      this.originalConsole.info(...args);
      this.sendLog('info', this.formatArgs(args));
    };

    console.debug = (...args: unknown[]) => {
      this.originalConsole.debug(...args);
      this.sendLog('debug', this.formatArgs(args));
    };

    console.warn = (...args: unknown[]) => {
      this.originalConsole.warn(...args);
      this.sendLog('warn', this.formatArgs(args));
    };

    console.error = (...args: unknown[]) => {
      this.originalConsole.error(...args);

      // Check if the first argument is an Error object
      if (args[0] instanceof Error) {
        const error = args[0];
        this.sendLog('error', error.message, 'Console', {
          name: error.name,
          message: error.message,
          stack: error.stack
        });
      } else {
        this.sendLog('error', this.formatArgs(args));
      }
    };

    // Global error handlers
    window.addEventListener('error', (event: ErrorEvent) => {
      this.originalConsole.error('Uncaught Error:', event.error);
      this.sendLog('error', event.message, 'Window Error', {
        name: event.error?.name || 'Error',
        message: event.message,
        stack: event.error?.stack
      });
    });

    window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => {
      this.originalConsole.error('Unhandled Promise Rejection:', event.reason);

      const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason));
      this.sendLog('error', error.message, 'Unhandled Rejection', {
        name: error.name,
        message: error.message,
        stack: error.stack
      });
    });
  }

  private formatArgs(args: unknown[]): string {
    return args.map(arg => {
      if (typeof arg === 'string') {
        return arg;
      }
      if (arg instanceof Error) {
        return `${arg.name}: ${arg.message}`;
      }
      try {
        return JSON.stringify(arg);
      } catch {
        return String(arg);
      }
    }).join(' ');
  }

  private sendLog(level: LogPayload['level'], message: string, context?: string, error?: LogPayload['error']): void {
    const payload: LogPayload = {
      level,
      message,
      context,
      error
    };

    this.http.post(`${environment.apiUrl}/log`, payload).subscribe({
      error: (err) => {
        this.originalConsole.error('Failed to send log to API:', err);
      }
    });
  }
}
