import https from 'https';
import { env } from '../config/env';
import type { ExecutionDriver } from './execution-driver';
import { AgentUnreachableError } from './execution-driver';
import type { ContainerInfo } from './docker.service';
import { logger } from '../utils/logger';

interface AgentRequestOptions {
  method: 'GET' | 'POST' | 'DELETE';
  path: string;
  body?: unknown;
  timeoutMs?: number;
}

/**
 * RemoteDriver makes HTTPS calls to a remote CCP agent for all operations.
 * Uses mTLS — both CCP (client) and agent (server) present certificates.
 */
export class RemoteDriver implements ExecutionDriver {
  constructor(
    private agentUrl: string,
    private slug: string,
    private clientCert: Buffer,
    private clientKey: Buffer,
    private caCert: Buffer
  ) {}

  // ─── HTTP Client ────────────────────────────────────────────

  private async request<T = unknown>(opts: AgentRequestOptions): Promise<T> {
    const url = new URL(opts.path, this.agentUrl);
    const timeoutMs = opts.timeoutMs || env.AGENT_REQUEST_TIMEOUT_MS;

    const payload = opts.body ? JSON.stringify(opts.body) : undefined;

    return new Promise<T>((resolve, reject) => {
      const req = https.request(
        {
          hostname: url.hostname,
          port: url.port || 7443,
          path: url.pathname + url.search,
          method: opts.method,
          headers: {
            'Content-Type': 'application/json',
            ...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}),
          },
          cert: this.clientCert,
          key: this.clientKey,
          ca: this.caCert,
          rejectUnauthorized: true,
          timeout: timeoutMs,
        },
        (res) => {
          let data = '';
          res.on('data', (chunk) => (data += chunk));
          res.on('end', () => {
            if (res.statusCode && res.statusCode >= 400) {
              try {
                const err = JSON.parse(data);
                reject(new Error(err.message || `Agent returned ${res.statusCode}`));
              } catch {
                reject(new Error(`Agent returned ${res.statusCode}: ${data.substring(0, 500)}`));
              }
              return;
            }

            try {
              resolve(data ? JSON.parse(data) as T : (undefined as T));
            } catch {
              resolve(data as unknown as T);
            }
          });
        }
      );

      req.on('error', (err) => {
        reject(new AgentUnreachableError(this.agentUrl, err));
      });

      req.on('timeout', () => {
        req.destroy();
        reject(new AgentUnreachableError(this.agentUrl, new Error(`Timed out after ${timeoutMs}ms`)));
      });

      if (payload) req.write(payload);
      req.end();
    });
  }

  // ─── Docker Compose Operations ──────────────────────────────

  async composeUp(_projectDir: string, _project: string, services?: string[]): Promise<string> {
    return this.request<string>({
      method: 'POST',
      path: `/instance/${this.slug}/up`,
      body: { services },
      timeoutMs: env.AGENT_LONG_OP_TIMEOUT_MS,
    });
  }

  async composeDown(_projectDir: string, _project: string, removeVolumes?: boolean): Promise<string> {
    return this.request<string>({
      method: 'POST',
      path: `/instance/${this.slug}/down`,
      body: { removeVolumes },
      timeoutMs: env.AGENT_LONG_OP_TIMEOUT_MS,
    });
  }

  async composeStop(_projectDir: string, _project: string): Promise<string> {
    return this.request<string>({
      method: 'POST',
      path: `/instance/${this.slug}/stop`,
    });
  }

  async composeRestart(_projectDir: string, _project: string, service?: string): Promise<string> {
    return this.request<string>({
      method: 'POST',
      path: `/instance/${this.slug}/restart`,
      body: { service },
    });
  }

  async composePull(_projectDir: string, _project: string): Promise<string> {
    return this.request<string>({
      method: 'POST',
      path: `/instance/${this.slug}/pull`,
      timeoutMs: env.AGENT_LONG_OP_TIMEOUT_MS,
    });
  }

  async composeBuild(_projectDir: string, _project: string): Promise<string> {
    return this.request<string>({
      method: 'POST',
      path: `/instance/${this.slug}/build`,
      timeoutMs: env.AGENT_LONG_OP_TIMEOUT_MS,
    });
  }

  async composePs(_projectDir: string, _project: string): Promise<ContainerInfo[]> {
    return this.request<ContainerInfo[]>({
      method: 'GET',
      path: `/instance/${this.slug}/ps`,
    });
  }

  async composeLogs(_projectDir: string, _project: string, service?: string, tail?: number, since?: string): Promise<string> {
    const params = new URLSearchParams();
    if (service) params.set('service', service);
    if (tail) params.set('tail', String(tail));
    if (since) params.set('since', since);
    const qs = params.toString() ? `?${params}` : '';

    return this.request<string>({
      method: 'GET',
      path: `/instance/${this.slug}/logs${qs}`,
    });
  }

  async composeExec(_projectDir: string, _project: string, service: string, command: string, timeoutMs?: number, envVars?: Record<string, string>): Promise<string> {
    return this.request<string>({
      method: 'POST',
      path: `/instance/${this.slug}/exec`,
      body: { service, command, envVars },
      timeoutMs: timeoutMs || env.AGENT_LONG_OP_TIMEOUT_MS,
    });
  }

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

  async waitForHealthy(containerName: string, timeoutMs = 60_000, pollIntervalMs = 2_000): Promise<boolean> {
    // For remote instances, poll the agent's ps endpoint
    const deadline = Date.now() + timeoutMs;
    while (Date.now() < deadline) {
      try {
        const containers = await this.composePs('', '');
        const container = containers.find((c) => c.name.includes(containerName) || c.service === containerName);
        if (container?.health === 'healthy') return true;
        if (container?.state === 'exited' || container?.state === 'dead') {
          throw new Error(`Container ${containerName} exited unexpectedly`);
        }
      } catch (err) {
        if (err instanceof AgentUnreachableError) throw err;
        // Other errors — keep polling
      }
      await new Promise((r) => setTimeout(r, pollIntervalMs));
    }
    throw new Error(`Container ${containerName} did not become healthy within ${timeoutMs}ms`);
  }

  async waitForHttp(url: string, timeoutMs = 120_000, pollIntervalMs = 3_000): Promise<boolean> {
    // The URL is a local URL on the remote host. We ask the agent to check it.
    // For now, poll the agent's health endpoint for the instance.
    const deadline = Date.now() + timeoutMs;
    while (Date.now() < deadline) {
      try {
        const containers = await this.composePs('', '');
        const apiContainer = containers.find((c) => c.service === 'api');
        if (apiContainer?.state === 'running' && apiContainer?.health === 'healthy') return true;
      } catch (err) {
        if (err instanceof AgentUnreachableError) throw err;
      }
      await new Promise((r) => setTimeout(r, pollIntervalMs));
    }
    throw new Error(`HTTP endpoint did not respond within ${timeoutMs}ms`);
  }

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

  async readEnvFile(_basePath: string): Promise<Record<string, string> | null> {
    try {
      return await this.request<Record<string, string>>({
        method: 'GET',
        path: `/instance/${this.slug}/env`,
      });
    } catch {
      return null;
    }
  }

  async writeFiles(_basePath: string, files: Array<{ relativePath: string; content: string }>): Promise<void> {
    await this.request({
      method: 'POST',
      path: `/instance/${this.slug}/files`,
      body: { files },
      timeoutMs: env.AGENT_LONG_OP_TIMEOUT_MS,
    });
  }

  async mkdir(_basePath: string, relativePath: string): Promise<void> {
    await this.request({
      method: 'POST',
      path: `/instance/${this.slug}/mkdir`,
      body: { path: relativePath },
    });
  }

  async fileExists(_basePath: string, relativePath: string): Promise<boolean> {
    try {
      await this.request({
        method: 'GET',
        path: `/instance/${this.slug}/env`, // reuse env endpoint as a proxy for file existence
      });
      return true;
    } catch {
      return false;
    }
  }

  async deleteDirectory(_dirPath: string): Promise<void> {
    // Remote directory deletion is handled by the agent during instance unregistration
    logger.warn('[remote-driver] deleteDirectory called — remote cleanup handled by agent');
  }

  async cloneSource(_basePath: string, gitRepo: string, gitBranch: string, excludes?: string[]): Promise<void> {
    await this.request({
      method: 'POST',
      path: `/instance/${this.slug}/clone-source`,
      body: { gitRepo, gitBranch, excludes },
      timeoutMs: env.AGENT_LONG_OP_TIMEOUT_MS,
    });
  }
}
