import yaml from 'js-yaml';

/**
 * Ollama deployment mode in the unified install:
 * - 'native'   — host's local Ollama; container reaches it via host.docker.internal
 * - 'docker'   — bundled Ollama service in the compose stack
 * - 'external' — explicit OLLAMA_URL (skip both native detection and the docker service)
 */
export type OllamaMode = 'native' | 'docker' | 'external';

/** Embedding backend selected at install time. */
export type EmbeddingProvider = 'ollama' | 'openai' | 'voyage';

/** Kept for support-mode compatibility in legacy install code. */
export type InstallMode = 'developer' | 'server' | 'support';

export interface LocalProjectMount {
  /** Project name (used as the basename under /projects/). */
  name: string;
  /** Absolute host path bind-mounted into the indexer container. */
  hostPath: string;
}

export interface UnifiedComposeConfig {
  ollamaMode: OllamaMode;
  /** Required when ollamaMode === 'external'. */
  ollamaUrl?: string;
  /** Embedding backend. Cloud providers skip Ollama entirely. Default: 'ollama'. */
  embeddingProvider?: EmbeddingProvider;
  /** When set, the dockerized Qdrant service is skipped. */
  qdrantUrl?: string;
  /** Stored in ~/.paparats/.env and referenced as ${QDRANT_API_KEY}. */
  qdrantApiKey?: string;
  /** Cron schedule for indexer (default: every 6 hours). */
  cron?: string;
  /** Local-path projects in projects.yml — added as bind-mounts on the indexer. */
  localProjects?: LocalProjectMount[];
  /** Absolute host path to ~/.paparats (used to mount projects.yml). */
  paparatsHome: string;
  /** Optional port overrides (test convenience). */
  ports?: {
    qdrant?: number;
    paparats?: number;
    ollama?: number;
    indexer?: number;
  };
}

interface ComposeService {
  image: string;
  container_name: string;
  ports?: string[];
  environment?: Record<string, string>;
  extra_hosts?: string[];
  volumes?: string[];
  depends_on?: Record<string, { condition: string }>;
  healthcheck?: Record<string, unknown>;
  deploy?: Record<string, unknown>;
  logging?: Record<string, unknown>;
  restart?: string;
  networks?: string[];
}

interface ComposeFile {
  services: Record<string, ComposeService>;
  volumes: Record<string, null>;
  networks: Record<string, { driver: string; name: string }>;
}

const HEADER = `# paparats-mcp — Docker Compose (generated by paparats install)
# Managed file — regenerate with: paparats install
`;

function qdrantService(port: number): ComposeService {
  return {
    image: 'qdrant/qdrant:latest',
    container_name: 'paparats-qdrant',
    ports: [`\${QDRANT_PORT:-${port}}:6333`],
    volumes: ['qdrant_data:/qdrant/storage'],
    healthcheck: {
      test: ['CMD', 'bash', '-c', 'exec 3<>/dev/tcp/127.0.0.1/6333 || exit 1'],
      interval: '10s',
      timeout: '3s',
      retries: 3,
      start_period: '15s',
    },
    deploy: {
      resources: {
        limits: { cpus: '1.0', memory: '1G' },
        reservations: { cpus: '0.25', memory: '256M' },
      },
    },
    logging: { driver: 'json-file', options: { 'max-size': '10m', 'max-file': '3' } },
    restart: 'unless-stopped',
    networks: ['paparats-net'],
  };
}

function ollamaService(port: number): ComposeService {
  return {
    image: 'ibaz/paparats-ollama:latest',
    container_name: 'paparats-ollama',
    ports: [`\${OLLAMA_PORT:-${port}}:11434`],
    volumes: ['ollama_data:/root/.ollama'],
    healthcheck: {
      test: ['CMD', 'ollama', 'list'],
      interval: '10s',
      timeout: '5s',
      retries: 5,
      start_period: '60s',
    },
    logging: { driver: 'json-file', options: { 'max-size': '10m', 'max-file': '3' } },
    restart: 'unless-stopped',
    networks: ['paparats-net'],
  };
}

function paparatsService(
  port: number,
  ollamaUrl: string,
  qdrantUrl: string,
  includeQdrantDep: boolean,
  qdrantApiKey: string | undefined,
  embeddingProvider: EmbeddingProvider
): ComposeService {
  const dependsOn: Record<string, { condition: string }> = {};
  if (includeQdrantDep) {
    dependsOn['qdrant'] = { condition: 'service_healthy' };
  }

  const env: Record<string, string> = {
    QDRANT_URL: qdrantUrl,
  };
  if (embeddingProvider === 'ollama') {
    env['OLLAMA_URL'] = ollamaUrl;
    env['OLLAMA_BATCH_SIZE'] = '${OLLAMA_BATCH_SIZE:-5}';
  } else {
    env['EMBEDDING_PROVIDER'] = embeddingProvider;
    if (embeddingProvider === 'openai') env['OPENAI_API_KEY'] = '${OPENAI_API_KEY:-}';
    if (embeddingProvider === 'voyage') env['VOYAGE_API_KEY'] = '${VOYAGE_API_KEY:-}';
  }
  if (qdrantApiKey) env['QDRANT_API_KEY'] = '${QDRANT_API_KEY}';
  // Resolve indexer over the docker-compose network so the analytics UI's
  // indexer tile reaches its sibling instead of the host loopback (which has
  // no service inside the container).
  env['PAPARATS_INDEXER_URL'] = 'http://paparats-indexer:9877';

  return {
    image: 'ibaz/paparats-server:latest',
    container_name: 'paparats-mcp',
    ports: [`\${PAPARATS_PORT:-${port}}:9876`],
    environment: env,
    extra_hosts: ['host.docker.internal:host-gateway'],
    volumes: ['paparats_data:/home/nodejs/.paparats'],
    depends_on: dependsOn,
    healthcheck: {
      test: [
        'CMD',
        'node',
        '-e',
        "require('http').get('http://localhost:9876/health', (r) => { process.exit(r.statusCode === 200 ? 0 : 1); }).on('error', () => process.exit(1));",
      ],
      interval: '30s',
      timeout: '3s',
      retries: 3,
      start_period: '10s',
    },
    deploy: {
      resources: {
        limits: { cpus: '2.0', memory: '2G' },
        reservations: { cpus: '0.5', memory: '512M' },
      },
    },
    logging: { driver: 'json-file', options: { 'max-size': '10m', 'max-file': '3' } },
    restart: 'unless-stopped',
    networks: ['paparats-net'],
  };
}

function indexerService(
  config: UnifiedComposeConfig,
  qdrantUrl: string,
  ollamaUrl: string,
  port: number,
  embeddingProvider: EmbeddingProvider
): ComposeService {
  const externalQdrant = !!config.qdrantUrl;
  const dockerOllama = config.ollamaMode === 'docker' && embeddingProvider === 'ollama';

  const dependsOn: Record<string, { condition: string }> = {};
  if (dockerOllama) dependsOn['ollama'] = { condition: 'service_healthy' };
  if (!externalQdrant) dependsOn['qdrant'] = { condition: 'service_healthy' };

  const env: Record<string, string> = {
    GITHUB_TOKEN: '${GITHUB_TOKEN:-}',
    CRON: `\${CRON:-${config.cron ?? '0 */6 * * *'}}`,
    QDRANT_URL: qdrantUrl,
  };
  if (embeddingProvider === 'ollama') {
    env['OLLAMA_URL'] = ollamaUrl;
    env['OLLAMA_BATCH_SIZE'] = '${OLLAMA_BATCH_SIZE:-5}';
  } else {
    env['EMBEDDING_PROVIDER'] = embeddingProvider;
    if (embeddingProvider === 'openai') env['OPENAI_API_KEY'] = '${OPENAI_API_KEY:-}';
    if (embeddingProvider === 'voyage') env['VOYAGE_API_KEY'] = '${VOYAGE_API_KEY:-}';
  }
  if (config.qdrantApiKey) env['QDRANT_API_KEY'] = '${QDRANT_API_KEY}';

  // Mount the whole ~/.paparats as /config (directory bind-mount, not single file)
  // so the indexer survives in-place rewrites of projects.yml — single-file
  // bind-mounts pin to the host inode and break on atomic rename or replace.
  const volumes = [
    'indexer_repos:/data/repos',
    'paparats_data:/home/nodejs/.paparats',
    `${config.paparatsHome}:/config:ro`,
  ];
  for (const proj of config.localProjects ?? []) {
    volumes.push(`${proj.hostPath}:/projects/${proj.name}:ro`);
  }

  return {
    image: 'ibaz/paparats-indexer:latest',
    container_name: 'paparats-indexer',
    environment: env,
    ports: [`\${INDEXER_PORT:-${port}}:9877`],
    volumes,
    depends_on: dependsOn,
    extra_hosts: ['host.docker.internal:host-gateway'],
    logging: { driver: 'json-file', options: { 'max-size': '10m', 'max-file': '3' } },
    restart: 'unless-stopped',
    networks: ['paparats-net'],
  };
}

/**
 * Generate the unified docker-compose.yml — qdrant (optional) + ollama (optional)
 * + paparats-server + paparats-indexer with mounts derived from `localProjects`.
 */
export function generateCompose(config: UnifiedComposeConfig): string {
  const qdrantPort = config.ports?.qdrant ?? 6333;
  const paparatsPort = config.ports?.paparats ?? 9876;
  const ollamaPort = config.ports?.ollama ?? 11434;
  const indexerPort = config.ports?.indexer ?? 9877;

  const embeddingProvider: EmbeddingProvider = config.embeddingProvider ?? 'ollama';
  const isCloud = embeddingProvider !== 'ollama';

  const externalQdrant = !!config.qdrantUrl;
  // Cloud providers don't use Ollama at all — drop the service and depends_on edges.
  const dockerOllama = !isCloud && config.ollamaMode === 'docker';

  const qdrantUrl = externalQdrant ? config.qdrantUrl! : 'http://qdrant:6333';
  // For cloud providers the URL is irrelevant (the server reads
  // EMBEDDING_PROVIDER instead) — pass an empty string so we don't accidentally
  // wire host.docker.internal when the host has no Ollama running.
  const ollamaUrl = isCloud ? '' : resolveOllamaUrl(config);

  const compose: ComposeFile = {
    services: {
      ...(externalQdrant ? {} : { qdrant: qdrantService(qdrantPort) }),
      ...(dockerOllama ? { ollama: ollamaService(ollamaPort) } : {}),
      paparats: paparatsService(
        paparatsPort,
        ollamaUrl,
        qdrantUrl,
        !externalQdrant,
        config.qdrantApiKey,
        embeddingProvider
      ),
      'paparats-indexer': indexerService(
        config,
        qdrantUrl,
        ollamaUrl,
        indexerPort,
        embeddingProvider
      ),
    },
    volumes: {
      ...(externalQdrant ? {} : { qdrant_data: null }),
      ...(dockerOllama ? { ollama_data: null } : {}),
      paparats_data: null,
      indexer_repos: null,
    },
    networks: {
      'paparats-net': { driver: 'bridge', name: 'paparats-network' },
    },
  };

  if (dockerOllama) {
    compose.services['paparats']!.depends_on!['ollama'] = { condition: 'service_healthy' };
  }

  return (
    HEADER +
    yaml.dump(compose, { lineWidth: 120, noRefs: true, quotingType: "'", forceQuotes: false })
  );
}

function resolveOllamaUrl(config: UnifiedComposeConfig): string {
  switch (config.ollamaMode) {
    case 'docker':
      return 'http://ollama:11434';
    case 'native':
      return 'http://host.docker.internal:11434';
    case 'external':
      if (!config.ollamaUrl) {
        throw new Error('generateCompose: ollamaMode="external" requires ollamaUrl');
      }
      return config.ollamaUrl;
  }
}
