/**
 * PULSE API sidecar manager.
 *
 * Auto-starts the PULSE API server as a child process if it's not already running.
 * Uses `python -m pulse.api.pulse_api` for cross-platform compatibility.
 */

import { execFile, spawn, type ChildProcess } from "node:child_process";
import { promisify } from "node:util";
import { existsSync } from "node:fs";
import { randomBytes } from "node:crypto";

const execFileAsync = promisify(execFile);

interface Logger {
  info(msg: string): void;
  warn(msg: string): void;
  error(msg: string): void;
}

export class PulseSidecar {
  private process: ChildProcess | null = null;
  private logger: Logger;
  private resolvedPython: string | null = null;
  private _sessionToken: string | null = null;

  constructor(logger: Logger) {
    this.logger = logger;
  }

  /** Session token for sidecar auth. Only set after start(). */
  get sessionToken(): string | null {
    return this._sessionToken;
  }

  /**
   * Resolve Python to an absolute path. Rejects bare PATH lookups.
   * Checks VIRTUAL_ENV, common install paths, then falls back to `where`/`which`.
   */
  private async resolvePythonPath(): Promise<string | null> {
    if (this.resolvedPython) return this.resolvedPython;

    const isWin = process.platform === "win32";

    // 1. Check VIRTUAL_ENV first
    const venv = process.env.VIRTUAL_ENV;
    if (venv) {
      const venvPython = isWin
        ? `${venv}\\Scripts\\python.exe`
        : `${venv}/bin/python`;
      if (existsSync(venvPython)) {
        this.resolvedPython = venvPython;
        return venvPython;
      }
    }

    // 2. Use where/which to resolve absolute path
    const locateCmd = isWin ? "where" : "which";
    for (const name of ["python", "python3"]) {
      try {
        const { stdout } = await execFileAsync(locateCmd, [name], {
          timeout: 5_000,
        });
        const resolved = stdout.trim().split(/\r?\n/)[0];
        if (resolved && existsSync(resolved)) {
          // Validate it's actually a python binary
          const { stdout: ver } = await execFileAsync(resolved, ["--version"], {
            timeout: 5_000,
          });
          if (ver.toLowerCase().includes("python")) {
            this.resolvedPython = resolved;
            this.logger.info(`[PULSE] Resolved Python: ${resolved}`);
            return resolved;
          }
        }
      } catch {
        // Try next candidate
      }
    }

    return null;
  }

  /** Check if the `pulse` Python package is importable. */
  async checkInstalled(): Promise<boolean> {
    const pythonPath = await this.resolvePythonPath();
    if (!pythonPath) return false;
    try {
      await execFileAsync(pythonPath, ["-c", "import pulse"], {
        timeout: 5_000,
      });
      return true;
    } catch {
      return false;
    }
  }

  /** Start the PULSE API server as a background child process. */
  async start(): Promise<void> {
    if (this.process) {
      this.logger.info("[PULSE] Sidecar already running.");
      return;
    }

    const installed = await this.checkInstalled();
    if (!installed) {
      this.logger.error(
        "[PULSE] Python package not found. Install with: pip install pulse-os"
      );
      return;
    }

    const pythonPath = this.resolvedPython!;
    this.logger.info(`[PULSE] Starting API sidecar via ${pythonPath}...`);

    // Generate ephemeral session token for sidecar auth
    this._sessionToken = randomBytes(32).toString("hex");

    this.process = spawn(pythonPath, ["-m", "pulse.api.pulse_api"], {
      stdio: "ignore",
      detached: true,
      env: { ...process.env, PULSE_SESSION_TOKEN: this._sessionToken },
    });

    this.process.unref();

    this.process.on("error", (err) => {
      this.logger.error(`[PULSE] Sidecar error: ${err.message}`);
      this.process = null;
      this._sessionToken = null;
    });

    this.process.on("exit", (code) => {
      if (code !== 0 && code !== null) {
        this.logger.warn(`[PULSE] Sidecar exited with code ${code}`);
      }
      this.process = null;
      this._sessionToken = null;
    });
  }

  /** Stop the sidecar process if running. */
  stop(): void {
    if (this.process) {
      this.process.kill("SIGTERM");
      this.process = null;
      this._sessionToken = null;
      this.logger.info("[PULSE] Sidecar stopped.");
    }
  }

  get isRunning(): boolean {
    return this.process !== null;
  }
}
