/**
 * Docker Compose Generator
 *
 * Generates docker-compose.preview.yml and Caddyfile for preview deployments.
 * All services share a Docker network; only Caddy publishes a host port.
 */

import yaml from 'js-yaml';
import type { PreviewConfig, PreviewServiceConfig } from './types.js';
import { PREVIEW_LABELS } from './types.js';

export interface ComposeGeneratorOptions {
  projectPath: string;
  port: number;
  deploymentId: string;
  labels: Record<string, string>;
}

interface ComposeService {
  build?: { context: string; dockerfile: string } | string;
  image?: string;
  environment?: Record<string, string>;
  volumes?: string[];
  labels?: Record<string, string>;
  networks?: string[];
  depends_on?: string[];
}

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

/**
 * Generate a Docker Compose YAML string for the preview deployment.
 *
 * Structure:
 * - User-defined services with build contexts
 * - Caddy reverse proxy publishing the single host port
 * - Shared `preview` network
 */
export function generateComposeFile(
  config: PreviewConfig,
  opts: ComposeGeneratorOptions,
): string {
  const compose: ComposeFile = {
    services: {},
    networks: {
      preview: { driver: 'bridge' },
    },
  };

  const serviceNames: string[] = [];

  // Add user-defined services
  for (const [name, svc] of Object.entries(config.services)) {
    serviceNames.push(name);
    const service: ComposeService = {
      labels: { ...opts.labels },
      networks: ['preview'],
    };

    // Build config
    if (svc.build) {
      if (typeof svc.build === 'string') {
        service.build = {
          context: opts.projectPath,
          dockerfile: svc.build === '.' ? 'Dockerfile' : svc.build,
        };
      } else {
        service.build = {
          context: svc.build.context.startsWith('/')
            ? svc.build.context
            : `${opts.projectPath}/${svc.build.context}`,
          dockerfile: svc.build.dockerfile,
        };
      }
    } else if (svc.image) {
      service.image = svc.image;
    }

    // Environment
    if (svc.env && Object.keys(svc.env).length > 0) {
      service.environment = svc.env;
    }

    // Volumes
    if (svc.volumes && svc.volumes.length > 0) {
      service.volumes = svc.volumes;
    }

    compose.services[name] = service;
  }

  // Generate and add Caddy proxy service
  const caddyfile = generateCaddyfile(config);
  const caddyService: ComposeService = {
    image: 'caddy:2-alpine',
    networks: ['preview'],
    labels: { ...opts.labels },
  };

  // Caddy publishes the single host port
  (caddyService as Record<string, unknown>).ports = [`${opts.port}:80`];

  // Mount Caddyfile via inline config
  (caddyService as Record<string, unknown>).command = ['caddy', 'run', '--config', '/etc/caddy/Caddyfile'];

  // Caddy config will be written to the deployment directory and mounted
  (caddyService as Record<string, unknown>).volumes = ['./Caddyfile:/etc/caddy/Caddyfile:ro'];

  if (serviceNames.length > 0) {
    caddyService.depends_on = serviceNames;
  }

  compose.services['caddy-proxy'] = caddyService;

  return yaml.dump(compose, { lineWidth: 120, noRefs: true });
}

/**
 * Generate a Caddyfile from route mappings in the preview config.
 *
 * Routes are sorted by specificity (longest path first) to ensure
 * more specific routes match before catch-all.
 */
export function generateCaddyfile(config: PreviewConfig): string {
  const routes: Array<{ name: string; route: string; port: number }> = [];

  for (const [name, svc] of Object.entries(config.services)) {
    if (svc.internal) continue;
    routes.push({
      name,
      route: svc.route ?? '/',
      port: svc.port,
    });
  }

  // Sort by route specificity (longer paths first, root last)
  routes.sort((a, b) => {
    if (a.route === '/') return 1;
    if (b.route === '/') return -1;
    return b.route.length - a.route.length;
  });

  const lines: string[] = [':80 {'];

  for (const route of routes) {
    if (route.route === '/') {
      lines.push(`  handle {`);
      lines.push(`    reverse_proxy ${route.name}:${route.port}`);
      lines.push(`  }`);
    } else {
      // Strip trailing slash for handle_path
      const path = route.route.endsWith('/') ? route.route.slice(0, -1) : route.route;
      lines.push(`  handle_path ${path}/* {`);
      lines.push(`    reverse_proxy ${route.name}:${route.port}`);
      lines.push(`  }`);
    }
  }

  lines.push('}');
  return lines.join('\n');
}

/**
 * Generate compose labels for a preview deployment.
 */
export function generateLabels(opts: {
  initiativeId: string;
  phaseId?: string;
  projectId: string;
  branch: string;
  port: number;
  previewId: string;
}): Record<string, string> {
  const labels: Record<string, string> = {
    [PREVIEW_LABELS.preview]: 'true',
    [PREVIEW_LABELS.initiativeId]: opts.initiativeId,
    [PREVIEW_LABELS.projectId]: opts.projectId,
    [PREVIEW_LABELS.branch]: opts.branch,
    [PREVIEW_LABELS.port]: String(opts.port),
    [PREVIEW_LABELS.previewId]: opts.previewId,
  };

  if (opts.phaseId) {
    labels[PREVIEW_LABELS.phaseId] = opts.phaseId;
  }

  return labels;
}
