import fs from 'fs/promises';
import path from 'path';
import { promisify } from 'util';
import { parse as parseDotenv } from 'dotenv';
import * as docker from './docker.service';
import type { ExecutionDriver } from './execution-driver';
import { logger } from '../utils/logger';

/**
 * LocalDriver wraps existing docker.service.ts functions and filesystem operations.
 * This is a zero-behavior-change adapter — all existing local instance operations
 * pass through unchanged.
 */
export class LocalDriver implements ExecutionDriver {
  // ─── Docker Compose Operations ──────────────────────────────

  composeUp(projectDir: string, project: string, services?: string[]) {
    return docker.composeUp(projectDir, project, services);
  }

  composeDown(projectDir: string, project: string, removeVolumes?: boolean) {
    return docker.composeDown(projectDir, project, removeVolumes);
  }

  composeStop(projectDir: string, project: string) {
    return docker.composeStop(projectDir, project);
  }

  composeRestart(projectDir: string, project: string, service?: string) {
    return docker.composeRestart(projectDir, project, service);
  }

  composePull(projectDir: string, project: string) {
    return docker.composePull(projectDir, project);
  }

  composeBuild(projectDir: string, project: string) {
    return docker.composeBuild(projectDir, project);
  }

  composePs(projectDir: string, project: string) {
    return docker.composePs(projectDir, project);
  }

  composeLogs(projectDir: string, project: string, service?: string, tail?: number, since?: string) {
    return docker.composeLogs(projectDir, project, service, tail, since);
  }

  composeExec(projectDir: string, project: string, service: string, command: string, timeoutMs?: number, envVars?: Record<string, string>) {
    return docker.composeExec(projectDir, project, service, command, timeoutMs, envVars);
  }

  // ─── Container Health ───────────────────────────────────────

  waitForHealthy(containerName: string, timeoutMs?: number, pollIntervalMs?: number) {
    return docker.waitForHealthy(containerName, timeoutMs, pollIntervalMs);
  }

  waitForHttp(url: string, timeoutMs?: number, pollIntervalMs?: number) {
    return docker.waitForHttp(url, timeoutMs, pollIntervalMs);
  }

  // ─── Filesystem Operations ──────────────────────────────────

  async readEnvFile(basePath: string): Promise<Record<string, string> | null> {
    try {
      const content = await fs.readFile(path.join(basePath, '.env'), 'utf-8');
      return parseDotenv(Buffer.from(content));
    } catch {
      return null;
    }
  }

  async writeFiles(basePath: string, files: Array<{ relativePath: string; content: string }>) {
    for (const file of files) {
      const filePath = path.join(basePath, file.relativePath);
      await fs.mkdir(path.dirname(filePath), { recursive: true });
      await fs.writeFile(filePath, file.content, 'utf-8');
      logger.debug(`[local-driver] Wrote ${filePath}`);
    }
  }

  async mkdir(basePath: string, relativePath: string) {
    await fs.mkdir(path.join(basePath, relativePath), { recursive: true });
  }

  async fileExists(basePath: string, relativePath: string): Promise<boolean> {
    try {
      await fs.access(path.join(basePath, relativePath));
      return true;
    } catch {
      return false;
    }
  }

  async deleteDirectory(dirPath: string) {
    await fs.rm(dirPath, { recursive: true, force: true });
  }

  async cloneSource(basePath: string, _gitRepo: string, _gitBranch: string, excludes?: string[]) {
    // Local provisioning uses rsync from CML_SOURCE_PATH
    const { CML_SOURCE_PATH } = await import('../config/env').then((m) => m.env);
    if (!CML_SOURCE_PATH) {
      throw new Error('CML_SOURCE_PATH not configured — cannot clone source');
    }

    // SECURITY: Validate exclude entries — reject anything with shell metacharacters
    const SAFE_EXCLUDE = /^[a-zA-Z0-9_.\/-]+$/;
    const safeExcludes = (excludes || [
      'node_modules', '.git', '.env', 'changemaker-control-panel', '.claude',
      'api/dist', 'admin/dist', 'uploads', 'data',
    ]).filter((e) => SAFE_EXCLUDE.test(e));

    // SECURITY: Use execFile with args array — no shell interpolation
    const { execFile: execFileCb } = await import('child_process');
    const execFileAsync = promisify(execFileCb);
    const args = ['-a', ...safeExcludes.flatMap((e) => ['--exclude', e]), `${CML_SOURCE_PATH}/`, `${basePath}/`];
    await execFileAsync('rsync', args, { timeout: 120_000 });
  }
}

/** Singleton local driver instance. */
let _localDriver: LocalDriver | null = null;

export function getLocalDriver(): LocalDriver {
  if (!_localDriver) {
    _localDriver = new LocalDriver();
  }
  return _localDriver;
}
