/**
 * Unified runtime-logs fetcher for Coolify apps.
 *
 * Coolify's `/applications/{uuid}/logs` REST endpoint works well for
 * single-container build packs (dockerfile, nixpacks, static) but
 * returns empty for `dockercompose` — it has no way to pick which of
 * N services to tail.
 *
 * For compose apps we SSH into the Coolify host and call `docker logs`
 * directly against each compose-managed container (Coolify names them
 * `{service}-{appUuid}-{shortHash}`). The SSH user is dedicated to this
 * purpose — a member of the `docker` group, no sudo, single ed25519
 * key.
 *
 * The agent-facing shape is always `{ services: {...}, source, warnings }`
 * regardless of which path was taken. That way the MCP tool response is
 * stable whether Coolify's API grows per-service support later or not.
 */

import { getApplication, getApplicationRuntimeLogsFromApi, type CoolifyApplication } from '@/lib/coolify';
import { isCoolifySshConfigured, runOnCoolifyHost } from '@/lib/coolify-ssh';
import { listContainersForApp } from '@/lib/coolify-containers';

export type LogsSource = 'coolify_api' | 'ssh_docker' | 'empty';

export interface ApplicationRuntimeLogs {
  uuid: string;
  buildPack: string;
  source: LogsSource;
  /**
   * Per-service logs. For non-compose apps there's a single entry keyed
   * by the application name (or "app"). For compose apps, one entry per
   * service (`server`, `db`, `redis`, etc.).
   */
  services: Record<string, ServiceLogs>;
  warnings: string[];
  truncated: boolean;
}

export interface ServiceLogs {
  container: string | null;
  lines: number;
  bytes: number;
  logs: string;
  /** Raw `docker ps` status string when we fetched over SSH. */
  status?: string;
}

const DEFAULT_LINES = 200;
const MAX_LINES = 5000;

export interface GetLogsOpts {
  /** Limit to a specific compose service (ignored for non-compose apps). */
  service?: string;
  /** Number of tail lines per container. Clamped to [1, 5000]. */
  lines?: number;
}

/**
 * Shell-escape a single token for bash. We never pass user input through
 * here — values are derived from Coolify state (uuids, service names) or
 * our own integer line counts — but defense-in-depth is cheap.
 */
function sq(s: string): string {
  return `'${s.replace(/'/g, `'\\''`)}'`;
}

export async function getApplicationRuntimeLogs(
  uuid: string,
  opts: GetLogsOpts = {},
): Promise<ApplicationRuntimeLogs> {
  const lines = Math.max(1, Math.min(opts.lines ?? DEFAULT_LINES, MAX_LINES));
  const app = await getApplication(uuid);
  const buildPack = (app.build_pack ?? 'unknown') as string;
  const warnings: string[] = [];

  // ── Compose path (needs SSH) ─────────────────────────────────────
  if (buildPack === 'dockercompose') {
    if (!isCoolifySshConfigured()) {
      warnings.push(
        'COOLIFY_SSH_* not configured — cannot tail per-service logs for compose apps. ' +
          'Set COOLIFY_SSH_HOST / COOLIFY_SSH_PRIVATE_KEY_B64 / COOLIFY_SSH_USER to enable.',
      );
      return {
        uuid,
        buildPack,
        source: 'empty',
        services: {},
        warnings,
        truncated: false,
      };
    }
    return await fetchComposeLogsViaSsh(app, lines, opts.service, warnings);
  }

  // ── Single-container path (Coolify API) ─────────────────────────
  try {
    const res = await getApplicationRuntimeLogsFromApi(uuid, lines);
    const raw = (res?.logs ?? '').toString();
    const serviceName = (app.name as string) || 'app';
    return {
      uuid,
      buildPack,
      source: raw ? 'coolify_api' : 'empty',
      services: {
        [serviceName]: {
          container: null,
          lines: raw ? raw.split('\n').length : 0,
          bytes: Buffer.byteLength(raw, 'utf8'),
          logs: raw,
        },
      },
      warnings: raw
        ? warnings
        : [...warnings, 'Coolify returned empty logs. App may not be running yet, or this is a known Coolify limitation for this runtime.'],
      truncated: false,
    };
  } catch (err) {
    warnings.push(`coolify_api error: ${err instanceof Error ? err.message : String(err)}`);
    // Fall through to SSH if we have it — handy for dockerfile apps too
    if (!isCoolifySshConfigured()) {
      return { uuid, buildPack, source: 'empty', services: {}, warnings, truncated: false };
    }
    return await fetchComposeLogsViaSsh(app, lines, opts.service, warnings);
  }
}

async function fetchComposeLogsViaSsh(
  app: CoolifyApplication,
  lines: number,
  filterService: string | undefined,
  warnings: string[],
): Promise<ApplicationRuntimeLogs> {
  const uuid = app.uuid as string;
  const buildPack = (app.build_pack ?? 'dockercompose') as string;

  // 1) Enumerate containers that belong to this app via the shared
  //    helper. Coolify's naming convention always includes the app
  //    uuid (suffix for compose, embedded for single-container).
  let containers: Array<{ name: string; status: string; service: string }>;
  try {
    const raw = await listContainersForApp(uuid);
    containers = raw.map(c => ({ name: c.name, status: c.status, service: c.service }));
  } catch (err) {
    warnings.push(err instanceof Error ? err.message : String(err));
    return { uuid, buildPack, source: 'empty', services: {}, warnings, truncated: false };
  }

  if (containers.length === 0) {
    warnings.push('No containers found on Coolify host for this app uuid. Deployment may not have started yet.');
    return { uuid, buildPack, source: 'ssh_docker', services: {}, warnings, truncated: false };
  }

  // 2) Tail logs per container. listContainersForApp already derives
  //    the service name from each container; we just need to filter
  //    by the caller's requested service (if any).
  const services: Record<string, ServiceLogs> = {};
  let anyTruncated = false;

  const targets = containers.filter(c => !filterService || c.service === filterService);
  if (filterService && targets.length === 0) {
    warnings.push(
      `No container matched service=${filterService}. Available: ${containers.map(c => c.service).join(', ')}`,
    );
  }

  for (const c of targets) {
    const key = services[c.service] ? c.name : c.service;
    const logsRes = await runOnCoolifyHost(
      `docker logs --tail ${Math.floor(lines)} --timestamps ${sq(c.name)} 2>&1`,
      { timeoutMs: 10_000, maxBytes: 1_000_000 },
    );
    if (logsRes.truncated) anyTruncated = true;
    const text = logsRes.stdout; // stderr merged via `2>&1`
    services[key] = {
      container: c.name,
      lines: text ? text.split('\n').length : 0,
      bytes: Buffer.byteLength(text, 'utf8'),
      logs: text,
      status: c.status,
    };
  }

  return {
    uuid,
    buildPack,
    source: 'ssh_docker',
    services,
    warnings,
    truncated: anyTruncated,
  };
}
