import { type ChildProcess, spawn } from 'node:child_process';
import { existsSync, rmdirSync } from 'node:fs';
import { join } from 'node:path';
import { is } from '@electron-toolkit/utils';
import { app } from 'electron';

type PackageEnvironment = 'flatpak' | 'appimage' | 'system' | 'mac' | 'dev';

export class RelayManager {
  private process: ChildProcess | null;
  private readonly relayPath: string;
  private isShuttingDown: boolean;
  private restartAttempts: number;
  private readonly maxRestartAttempts: number;
  private readonly restartDelay: number;
  private restartTimeout: NodeJS.Timeout | null;
  private relayLogs: string[];
  private readonly maxLogs: number;
  private encryptionKey: string | null;
  private devRunning: boolean;

  /**
   * Checks if the relay is currently running.
   *
   * @returns {boolean} True if the relay is running, otherwise false.
   */
  get isRunning(): boolean {
    if (!this.process) return false;
    if (is.dev) return this.devRunning;
    return true;
  }

  /**
   * Retrieves the process identifier (PID) of the running relay process.
   *
   * @returns {number | undefined} The PID of the relay process if it is running, otherwise undefined.
   */

  get pid(): number | undefined {
    return this.process?.pid;
  }

  constructor(maxRestartAttempts = 5, restartDelay = 1000, maxLogs = 100) {
    this.process = null;
    this.relayPath = this.getRelayPath();
    this.isShuttingDown = false;
    this.restartAttempts = 0;
    this.maxRestartAttempts = maxRestartAttempts;
    this.restartDelay = restartDelay;
    this.restartTimeout = null;
    this.relayLogs = [];
    this.maxLogs = maxLogs;
    this.encryptionKey = null;
    this.devRunning = false;
  }

  private detectEnvironment(): PackageEnvironment {
    if (is.dev) return 'dev';
    if (process.platform === 'darwin') return 'mac';
    if (process.env.FLATPAK_ID) return 'flatpak';
    if (process.env.APPIMAGE) return 'appimage';
    return 'system';
  }

  private getRelayPath(): string {
    const environment = this.detectEnvironment();

    switch (environment) {
      case 'dev':
        return join(__dirname, '../../extras/linux/relay');
      case 'mac':
        return join(process.resourcesPath, 'macos', 'eve-relay');
      case 'flatpak':
        return '/app/lib/com.arx_ccn.eve/usr/bin/eve-relay';
      case 'appimage':
        return join(process.env.APPDIR || '', 'usr/bin/eve-relay');
      case 'system':
        return '/usr/bin/eve-relay';
    }
  }

  public getRelayConfigPath(): string {
    const environment = this.detectEnvironment();
    const xdgConfigHome = process.env.XDG_CONFIG_HOME ?? join(app.getPath('home'), '.config');
    const isLinux = environment in ['dev', 'system', 'appimage', 'flatpak'];
    const configPath = isLinux
      ? join(xdgConfigHome, 'arx', 'Eve')
      : join(app.getPath('home'), 'Library', 'Application Support', 'eve', 'arx', 'Eve');
    return configPath;
  }

  private handleProcessExit(code: number | null): void {
    this.process = null;
    console.log(`Relay exited with code ${code}`);

    if (!this.isShuttingDown) this.restartProcess();
  }

  private restartProcess(): void {
    if (this.restartAttempts >= this.maxRestartAttempts) {
      console.error(`Failed to restart relay after ${this.maxRestartAttempts} attempts`);
      return;
    }

    this.restartAttempts++;
    console.log(`Attempting restart #${this.restartAttempts} in ${this.restartDelay}ms...`);

    if (this.restartTimeout) clearTimeout(this.restartTimeout);

    this.restartTimeout = setTimeout(() => {
      this.start(this.encryptionKey!);
    }, this.restartDelay);
  }

  private addLog(data: string): void {
    this.relayLogs.push(data);
    if (this.relayLogs.length > this.maxLogs) {
      this.relayLogs = this.relayLogs.slice(-this.maxLogs);
    }
  }

  /**
   * Start the Eve Relay.
   *
   * If the process is already running, do nothing.
   *
   * Logs from the Relay process are captured and can be retrieved with the
   * `getLogs()` method.
   *
   * If the process exits unexpectedly, it will be restarted according to the
   * configured restart policy.
   *
   * @param {string} encryptionKey - The key to use for encrypting data saved in
   *   the Relay.
   */
  public start(encryptionKey: string): void {
    if (this.process) return;

    this.encryptionKey = encryptionKey;

    if (is.dev) {
      this.relayLogs = [];

      this.process = spawn('nc', ['localhost', '6942']);
      this.devRunning = true;

      this.process.on('error', (err: Error) => {
        this.addLog(`[DEV] Failed to start netcat: ${err.message}`);
        this.process = null;
        this.devRunning = false;
        this.restartProcess();
      });

      this.process.on('exit', (code: number | null) => {
        this.addLog(`[DEV] Netcat exited with code ${code}`);
        this.process = null;
        this.devRunning = false;

        if (code !== 1) this.start(encryptionKey);
        else this.addLog('[DEV] Connection failed - target port may not be open - relay is not running');
      });
      return;
    }

    try {
      this.process = spawn(this.relayPath, [], {
        env: {
          ...process.env,
          LD_LIBRARY_PATH: process.env.LD_LIBRARY_PATH,
          PATH: process.env.PATH,
          ENCRYPTION_KEY: encryptionKey,
        },
      });

      if (this.process.stdout) {
        this.process.stdout.on('data', (data: Buffer) => {
          const logLine = data.toString().trim();
          this.addLog(logLine);
          console.log(logLine);
        });
      }

      if (this.process.stderr) {
        this.process.stderr.on('data', (data: Buffer) => {
          const logLine = data.toString().trim();
          this.addLog(logLine);
          console.error(logLine);
        });
      }

      this.process.on('error', (err: Error) => {
        console.error(`Failed to start Relay: ${err.message}`);
        this.process = null;
        this.restartProcess();
      });

      this.process.on('exit', this.handleProcessExit.bind(this));

      if (this.process.pid) this.restartAttempts = 0;
    } catch (error) {
      console.error(`Error starting Relay: ${error instanceof Error ? error.message : 'Unknown error'}`);
      this.restartProcess();
    }
  }

  /**
   * Stop the Eve Relay.
   *
   * If the Relay process is currently running, it will be terminated.
   *
   * If the process is not running, do nothing.
   *
   * All pending restarts will be cancelled.
   */
  public stop(): void {
    this.isShuttingDown = true;

    if (this.restartTimeout) {
      clearTimeout(this.restartTimeout);
      this.restartTimeout = null;
    }

    if (this.process) {
      this.process.kill();
      this.process = null;
    }
  }

  /**
   * Returns a copy of all the log lines the Relay has written to date.
   *
   * The returned array is a copy of the internal log state, and will not be
   * modified by future log activity.
   *
   * @returns A copy of all log lines from the Relay since it was last started.
   */
  public getLogs(): string[] {
    return [...this.relayLogs];
  }

  public reset(): void {
    this.stop();
    this.process = null;
    this.relayLogs = [];
    this.restartAttempts = 0;
    this.restartTimeout = null;
    const configPath = this.getRelayConfigPath();
    if (existsSync(configPath)) rmdirSync(configPath, { recursive: true });
  }
}
