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

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

  it('generates valid compose YAML with user services and Caddy proxy', () => {
    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 both user service and caddy
    expect(parsed.services.app).toBeDefined();
    expect(parsed.services['caddy-proxy']).toBeDefined();

    // Network present
    expect(parsed.networks.preview).toBeDefined();

    // Caddy publishes port
    expect(parsed.services['caddy-proxy'].ports).toContain('9100:80');

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

  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('caddy depends on all user services', () => {
    const config: PreviewConfig = {
      version: 1,
      services: {
        frontend: { name: 'frontend', build: '.', port: 3000 },
        backend: { name: 'backend', build: '.', port: 8080 },
      },
    };

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

    expect(parsed.services['caddy-proxy'].depends_on).toContain('frontend');
    expect(parsed.services['caddy-proxy'].depends_on).toContain('backend');
  });
});

describe('generateCaddyfile', () => {
  it('generates simple single-service Caddyfile', () => {
    const config: PreviewConfig = {
      version: 1,
      services: {
        app: { name: 'app', build: '.', port: 3000 },
      },
    };

    const caddyfile = generateCaddyfile(config);
    expect(caddyfile).toContain(':80 {');
    expect(caddyfile).toContain('reverse_proxy app:3000');
    expect(caddyfile).toContain('}');
  });

  it('generates multi-service Caddyfile with handle_path for non-root routes', () => {
    const config: PreviewConfig = {
      version: 1,
      services: {
        frontend: { name: 'frontend', build: '.', port: 3000, route: '/' },
        backend: { name: 'backend', build: '.', port: 8080, route: '/api' },
      },
    };

    const caddyfile = generateCaddyfile(config);
    expect(caddyfile).toContain('handle_path /api/*');
    expect(caddyfile).toContain('reverse_proxy backend:8080');
    expect(caddyfile).toContain('handle {');
    expect(caddyfile).toContain('reverse_proxy frontend:3000');
  });

  it('excludes internal services from Caddyfile', () => {
    const config: PreviewConfig = {
      version: 1,
      services: {
        app: { name: 'app', build: '.', port: 3000 },
        db: { name: 'db', image: 'postgres', port: 5432, internal: true },
      },
    };

    const caddyfile = generateCaddyfile(config);
    expect(caddyfile).not.toContain('postgres');
    expect(caddyfile).not.toContain('db:5432');
  });

  it('sorts routes by specificity (longer paths first)', () => {
    const config: PreviewConfig = {
      version: 1,
      services: {
        app: { name: 'app', build: '.', port: 3000, route: '/' },
        api: { name: 'api', build: '.', port: 8080, route: '/api' },
        auth: { name: 'auth', build: '.', port: 9090, route: '/api/auth' },
      },
    };

    const caddyfile = generateCaddyfile(config);
    const apiAuthIdx = caddyfile.indexOf('/api/auth');
    const apiIdx = caddyfile.indexOf('handle_path /api/*');
    const handleIdx = caddyfile.indexOf('handle {');

    // /api/auth should come before /api which should come before /
    expect(apiAuthIdx).toBeLessThan(apiIdx);
    expect(apiIdx).toBeLessThan(handleIdx);
  });
});

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

    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');
  });

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

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