import { describe, it, expect } from 'vitest';
import yaml from 'js-yaml';
import {
  generateComposeFile,
  generateLabels,
} from './compose-generator.js';
import { generateGatewayCaddyfile } from './gateway.js';
import type { PreviewConfig } from './types.js';
import { GATEWAY_NETWORK } from './types.js';
import type { GatewayRoute } from './gateway.js';

describe('generateComposeFile', () => {
  const baseOpts = {
    projectPath: '/workspace/repos/my-project-abc123',
    deploymentId: 'test123',
    labels: {
      'cw.preview': 'true',
      'cw.initiative-id': 'init-1',
      'cw.port': '9100',
    },
    mode: 'preview' as const,
  };

  it('generates valid compose YAML with user services (no Caddy sidecar)', () => {
    const config: PreviewConfig = {
      version: 1,
      services: {
        app: {
          name: 'app',
          build: '.',
          port: 3000,
        },
      },
    };

    const result = generateComposeFile(config, baseOpts);
    const parsed = yaml.load(result) as Record<string, any>;

    // Has user service but NOT caddy-proxy
    expect(parsed.services.app).toBeDefined();
    expect(parsed.services['caddy-proxy']).toBeUndefined();

    // External network + internal network
    expect(parsed.networks[GATEWAY_NETWORK]).toEqual({ external: true });
    expect(parsed.networks.internal).toEqual({ driver: 'bridge' });

    // No published ports on user service
    expect(parsed.services.app.ports).toBeUndefined();

    // Container name set for DNS resolution
    expect(parsed.services.app.container_name).toBe('cw-preview-test123-app');

    // Labels propagated
    expect(parsed.services.app.labels['cw.preview']).toBe('true');

    // Public service on both networks
    expect(parsed.services.app.networks).toContain(GATEWAY_NETWORK);
    expect(parsed.services.app.networks).toContain('internal');
  });

  it('handles object build config with context path joining', () => {
    const config: PreviewConfig = {
      version: 1,
      services: {
        api: {
          name: 'api',
          build: { context: 'packages/api', dockerfile: 'Dockerfile.prod' },
          port: 8080,
        },
      },
    };

    const result = generateComposeFile(config, baseOpts);
    const parsed = yaml.load(result) as Record<string, any>;

    expect(parsed.services.api.build.context).toBe(
      '/workspace/repos/my-project-abc123/packages/api',
    );
    expect(parsed.services.api.build.dockerfile).toBe('Dockerfile.prod');
  });

  it('handles image-based services', () => {
    const config: PreviewConfig = {
      version: 1,
      services: {
        db: {
          name: 'db',
          image: 'postgres:16',
          port: 5432,
          internal: true,
          env: { POSTGRES_PASSWORD: 'test' },
        },
      },
    };

    const result = generateComposeFile(config, baseOpts);
    const parsed = yaml.load(result) as Record<string, any>;

    expect(parsed.services.db.image).toBe('postgres:16');
    expect(parsed.services.db.environment.POSTGRES_PASSWORD).toBe('test');
  });

  it('internal services only get internal network', () => {
    const config: PreviewConfig = {
      version: 1,
      services: {
        db: {
          name: 'db',
          image: 'postgres:16',
          port: 5432,
          internal: true,
        },
      },
    };

    const result = generateComposeFile(config, baseOpts);
    const parsed = yaml.load(result) as Record<string, any>;

    expect(parsed.services.db.networks).toEqual(['internal']);
    expect(parsed.services.db.networks).not.toContain(GATEWAY_NETWORK);
  });

  it('dev mode uses image + volumes + command instead of build', () => {
    const config: PreviewConfig = {
      version: 1,
      services: {
        frontend: {
          name: 'frontend',
          build: '.',
          port: 3000,
          dev: {
            image: 'node:20-alpine',
            command: 'npm run dev -- --host 0.0.0.0',
            workdir: '/app',
          },
        },
      },
    };

    const devOpts = { ...baseOpts, mode: 'dev' as const };
    const result = generateComposeFile(config, devOpts);
    const parsed = yaml.load(result) as Record<string, any>;

    // Should use dev image, not build
    expect(parsed.services.frontend.image).toBe('node:20-alpine');
    expect(parsed.services.frontend.build).toBeUndefined();
    expect(parsed.services.frontend.command).toBe('npm run dev -- --host 0.0.0.0');
    expect(parsed.services.frontend.working_dir).toBe('/app');

    // Should have volume mount + node_modules anonymous volume
    expect(parsed.services.frontend.volumes).toContain(
      `${baseOpts.projectPath}:/app`,
    );
    expect(parsed.services.frontend.volumes).toContain('/app/node_modules');
  });
});

describe('generateGatewayCaddyfile', () => {
  it('generates single-preview Caddyfile with subdomain routing', () => {
    const previews = new Map<string, GatewayRoute[]>();
    previews.set('abc123', [
      { containerName: 'cw-preview-abc123-app', port: 3000, route: '/' },
    ]);

    const caddyfile = generateGatewayCaddyfile(previews, 9100);
    expect(caddyfile).toContain('auto_https off');
    expect(caddyfile).toContain('abc123.localhost:80 {');
    expect(caddyfile).toContain('handle /* {');
    expect(caddyfile).toContain('reverse_proxy cw-preview-abc123-app:3000');
  });

  it('generates multi-service Caddyfile with handle_path for non-root routes', () => {
    const previews = new Map<string, GatewayRoute[]>();
    previews.set('abc123', [
      { containerName: 'cw-preview-abc123-frontend', port: 3000, route: '/' },
      { containerName: 'cw-preview-abc123-backend', port: 8080, route: '/api' },
    ]);

    const caddyfile = generateGatewayCaddyfile(previews, 9100);
    expect(caddyfile).toContain('abc123.localhost:80 {');
    expect(caddyfile).toContain('handle_path /api/*');
    expect(caddyfile).toContain('reverse_proxy cw-preview-abc123-backend:8080');
    expect(caddyfile).toContain('handle /* {');
    expect(caddyfile).toContain('reverse_proxy cw-preview-abc123-frontend:3000');
  });

  it('generates separate subdomain blocks for each preview', () => {
    const previews = new Map<string, GatewayRoute[]>();
    previews.set('abc', [
      { containerName: 'cw-preview-abc-app', port: 3000, route: '/' },
    ]);
    previews.set('xyz', [
      { containerName: 'cw-preview-xyz-app', port: 5000, route: '/' },
    ]);

    const caddyfile = generateGatewayCaddyfile(previews, 9100);
    expect(caddyfile).toContain('abc.localhost:80 {');
    expect(caddyfile).toContain('xyz.localhost:80 {');
    expect(caddyfile).toContain('reverse_proxy cw-preview-abc-app:3000');
    expect(caddyfile).toContain('reverse_proxy cw-preview-xyz-app:5000');
  });

  it('sorts routes by specificity (longer paths first)', () => {
    const previews = new Map<string, GatewayRoute[]>();
    previews.set('abc', [
      { containerName: 'cw-preview-abc-app', port: 3000, route: '/' },
      { containerName: 'cw-preview-abc-api', port: 8080, route: '/api' },
      { containerName: 'cw-preview-abc-auth', port: 9090, route: '/api/auth' },
    ]);

    const caddyfile = generateGatewayCaddyfile(previews, 9100);
    const apiAuthIdx = caddyfile.indexOf('/api/auth');
    const apiIdx = caddyfile.indexOf('handle_path /api/*');
    const rootIdx = caddyfile.indexOf('handle /* {');

    expect(apiAuthIdx).toBeLessThan(apiIdx);
    expect(apiIdx).toBeLessThan(rootIdx);
  });
});

describe('generateLabels', () => {
  it('generates correct labels', () => {
    const labels = generateLabels({
      initiativeId: 'init-1',
      phaseId: 'phase-1',
      projectId: 'proj-1',
      branch: 'feature/test',
      gatewayPort: 9100,
      previewId: 'abc123',
      mode: 'preview',
    });

    expect(labels['cw.preview']).toBe('true');
    expect(labels['cw.initiative-id']).toBe('init-1');
    expect(labels['cw.phase-id']).toBe('phase-1');
    expect(labels['cw.project-id']).toBe('proj-1');
    expect(labels['cw.branch']).toBe('feature/test');
    expect(labels['cw.port']).toBe('9100');
    expect(labels['cw.preview-id']).toBe('abc123');
    expect(labels['cw.mode']).toBe('preview');
  });

  it('omits phaseId label when not provided', () => {
    const labels = generateLabels({
      initiativeId: 'init-1',
      projectId: 'proj-1',
      branch: 'main',
      gatewayPort: 9100,
      previewId: 'abc123',
      mode: 'dev',
    });

    expect(labels['cw.phase-id']).toBeUndefined();
    expect(labels['cw.mode']).toBe('dev');
  });
});
