import type { ContainerInfo } from './docker.service';

/**
 * Abstraction layer for instance operations.
 * LocalDriver wraps docker.service.ts + filesystem.
 * RemoteDriver makes HTTPS calls to the remote agent.
 */
export interface ExecutionDriver {
  // ─── Docker Compose Operations ──────────────────────────────
  composeUp(projectDir: string, project: string, services?: string[]): Promise<string>;
  composeDown(projectDir: string, project: string, removeVolumes?: boolean): Promise<string>;
  composeStop(projectDir: string, project: string): Promise<string>;
  composeRestart(projectDir: string, project: string, service?: string): Promise<string>;
  composePull(projectDir: string, project: string): Promise<string>;
  composeBuild(projectDir: string, project: string): Promise<string>;
  composePs(projectDir: string, project: string): Promise<ContainerInfo[]>;
  composeLogs(projectDir: string, project: string, service?: string, tail?: number, since?: string): Promise<string>;
  composeExec(projectDir: string, project: string, service: string, command: string, timeoutMs?: number, envVars?: Record<string, string>): Promise<string>;

  // ─── Container Health ───────────────────────────────────────
  waitForHealthy(containerName: string, timeoutMs?: number, pollIntervalMs?: number): Promise<boolean>;
  waitForHttp(url: string, timeoutMs?: number, pollIntervalMs?: number): Promise<boolean>;

  // ─── Filesystem Operations ──────────────────────────────────
  readEnvFile(basePath: string): Promise<Record<string, string> | null>;
  writeFiles(basePath: string, files: Array<{ relativePath: string; content: string }>): Promise<void>;
  // Approach C pre-flight: diff proposed file contents against on-disk current.
  // Returns per-file status (unchanged | modified | created) + unified diff for modified.
  // Read-only.
  diffFiles(
    basePath: string,
    files: Array<{ relativePath: string; content: string }>
  ): Promise<Array<{ path: string; status: 'unchanged' | 'modified' | 'created'; diff: string | null; sizeBefore: number; sizeAfter: number }>>;
  // Approach C: patch specific .env keys in place (install.sh-tenant path
  // where CCP can't re-render the full .env).
  patchEnv(basePath: string, vars: Record<string, string>): Promise<{ patched: string[]; added: string[] }>;
  mkdir(basePath: string, relativePath: string): Promise<void>;
  fileExists(basePath: string, relativePath: string): Promise<boolean>;
  deleteDirectory(dirPath: string): Promise<void>;
  cloneSource(basePath: string, gitRepo: string, gitBranch: string, excludes?: string[]): Promise<void>;
}

/**
 * Error thrown when a remote agent is unreachable.
 */
export class AgentUnreachableError extends Error {
  constructor(public agentUrl: string, cause?: Error) {
    super(`Remote agent at ${agentUrl} is not reachable`);
    this.name = 'AgentUnreachableError';
    if (cause) this.cause = cause;
  }
}

/**
 * Minimal instance shape needed to resolve a driver.
 */
export interface DriverInstance {
  id: string;
  slug: string;
  isRemote: boolean;
  agentUrl: string | null;
}

/**
 * Resolve the correct execution driver for an instance.
 * Returns LocalDriver for local instances, RemoteDriver for remote ones.
 */
export async function getDriverForInstance(instance: DriverInstance): Promise<ExecutionDriver> {
  if (!instance.isRemote) {
    const { getLocalDriver } = await import('./local-driver');
    return getLocalDriver();
  }
  return getRemoteDriverForInstance(instance);
}

/**
 * Resolve a RemoteDriver for a remote instance. Throws if the instance is
 * local, missing an agent URL, or has no valid mTLS certificate.
 *
 * Use this when you need to call RemoteDriver-specific methods like
 * createBackup() that don't exist on the ExecutionDriver interface.
 */
export async function getRemoteDriverForInstance(instance: DriverInstance) {
  if (!instance.isRemote) {
    throw new Error(`Instance ${instance.slug} is not remote`);
  }
  if (!instance.agentUrl) {
    throw new Error(`Remote instance ${instance.slug} has no agent URL configured`);
  }

  const { getAgentClientMaterials } = await import('./certificate.service');
  const materials = await getAgentClientMaterials(instance.id);
  if (!materials) {
    throw new Error(`No valid certificate found for remote instance ${instance.slug}`);
  }

  const { RemoteDriver } = await import('./remote-driver');
  return new RemoteDriver(
    instance.agentUrl,
    instance.slug,
    Buffer.from(materials.agentCertPem),
    Buffer.from(materials.agentKeyPem),
    Buffer.from(materials.caCertPem)
  );
}
