/**
 * Log Manager
 *
 * Manages log directories and file paths for per-process logging.
 */

import { mkdir, readdir, rm, stat } from 'node:fs/promises';
import { homedir } from 'node:os';
import { join } from 'node:path';
import type { LogConfig, LogStream } from './types.js';

/**
 * Default base directory for logs: ~/.cw/logs
 */
const DEFAULT_LOG_DIR = join(homedir(), '.cw', 'logs');

/**
 * Manages log directory structure and file paths.
 *
 * Log directory structure:
 * ~/.cw/logs/{processId}/stdout.log
 * ~/.cw/logs/{processId}/stderr.log
 */
export class LogManager {
  private readonly baseDir: string;
  private readonly retainDays?: number;

  constructor(config?: Partial<LogConfig>) {
    this.baseDir = config?.baseDir ?? DEFAULT_LOG_DIR;
    this.retainDays = config?.retainDays;
  }

  /**
   * Ensures the base log directory exists.
   * Creates it recursively if it doesn't exist.
   */
  async ensureLogDir(): Promise<void> {
    await mkdir(this.baseDir, { recursive: true });
  }

  /**
   * Ensures the process-specific log directory exists.
   * @param processId - The process identifier
   */
  async ensureProcessDir(processId: string): Promise<void> {
    const processDir = this.getProcessDir(processId);
    await mkdir(processDir, { recursive: true });
  }

  /**
   * Gets the directory path for a specific process's logs.
   * @param processId - The process identifier
   */
  getProcessDir(processId: string): string {
    return join(this.baseDir, processId);
  }

  /**
   * Gets the full path to a log file for a process and stream.
   * @param processId - The process identifier
   * @param stream - Either 'stdout' or 'stderr'
   */
  getLogPath(processId: string, stream: LogStream): string {
    return join(this.baseDir, processId, `${stream}.log`);
  }

  /**
   * Lists all log directories (one per process).
   * @returns Array of process IDs that have log directories
   */
  async listLogs(): Promise<string[]> {
    try {
      const entries = await readdir(this.baseDir, { withFileTypes: true });
      return entries
        .filter((entry) => entry.isDirectory())
        .map((entry) => entry.name);
    } catch (error) {
      // If directory doesn't exist, return empty list
      if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
        return [];
      }
      throw error;
    }
  }

  /**
   * Removes log directories older than the specified number of days.
   * @param retainDays - Number of days to retain logs (uses config value if not provided)
   * @returns Number of directories removed
   */
  async cleanOldLogs(retainDays?: number): Promise<number> {
    const days = retainDays ?? this.retainDays;
    if (days === undefined) {
      return 0;
    }

    const cutoffTime = Date.now() - days * 24 * 60 * 60 * 1000;
    const processIds = await this.listLogs();
    let removedCount = 0;

    for (const processId of processIds) {
      const processDir = this.getProcessDir(processId);
      try {
        const stats = await stat(processDir);
        if (stats.mtime.getTime() < cutoffTime) {
          await rm(processDir, { recursive: true, force: true });
          removedCount++;
        }
      } catch {
        // Skip if we can't stat or remove the directory
      }
    }

    return removedCount;
  }

  /**
   * Gets the base directory path.
   */
  getBaseDir(): string {
    return this.baseDir;
  }
}
