import { describe, it, expect, vi, beforeEach } from 'vitest';
import { DockerContainerManager } from '../src/services/docker/container-manager.js';
import type { ContainerSpec } from '../src/services/orchestrator.js';

// Mock dockerode
vi.mock('dockerode', () => {
  const mockContainer = {
    id: 'ctr-abc123',
    start: vi.fn(async () => {}),
    stop: vi.fn(async () => {}),
    remove: vi.fn(async () => {}),
    inspect: vi.fn(async () => ({
      Id: 'ctr-abc123',
      Name: '/mcpctl-test',
      State: { Status: 'running' },
      Created: '2024-01-01T00:00:00Z',
      NetworkSettings: {
        Ports: {
          '3000/tcp': [{ HostIp: '0.0.0.0', HostPort: '32768' }],
        },
      },
    })),
    logs: vi.fn(async () => Buffer.from('test log output')),
  };

  const mockModem = {
    followProgress: vi.fn((_stream: unknown, onFinished: (err: Error | null) => void) => {
      onFinished(null);
    }),
  };

  class MockDocker {
    modem = mockModem;
    ping = vi.fn(async () => 'OK');
    pull = vi.fn(async () => ({}));
    createContainer = vi.fn(async () => mockContainer);
    getContainer = vi.fn(() => mockContainer);
  }

  return { default: MockDocker };
});

describe('DockerContainerManager', () => {
  let manager: DockerContainerManager;

  beforeEach(() => {
    vi.clearAllMocks();
    manager = new DockerContainerManager();
  });

  describe('ping', () => {
    it('returns true when Docker is available', async () => {
      expect(await manager.ping()).toBe(true);
    });
  });

  describe('pullImage', () => {
    it('pulls an image', async () => {
      await manager.pullImage('node:20-alpine');
      // No error = success
    });
  });

  describe('createContainer', () => {
    it('creates and starts a container', async () => {
      const spec: ContainerSpec = {
        image: 'ghcr.io/slack-mcp:latest',
        name: 'mcpctl-slack-inst1',
        env: { SLACK_TOKEN: 'xoxb-test' },
        containerPort: 3000,
        hostPort: null,
        labels: { 'mcpctl.server-id': 'srv-1' },
      };

      const result = await manager.createContainer(spec);

      expect(result.containerId).toBe('ctr-abc123');
      expect(result.name).toBe('mcpctl-test');
      expect(result.state).toBe('running');
      expect(result.port).toBe(32768);
    });

    it('applies resource limits', async () => {
      const spec: ContainerSpec = {
        image: 'test:latest',
        name: 'test-container',
        memoryLimit: 256 * 1024 * 1024,
        nanoCpus: 250_000_000,
      };

      await manager.createContainer(spec);
      // The mock captures the call - we verify it doesn't throw
    });
  });

  describe('stopContainer', () => {
    it('stops a container', async () => {
      await manager.stopContainer('ctr-abc123');
      // No error = success
    });
  });

  describe('removeContainer', () => {
    it('removes a container', async () => {
      await manager.removeContainer('ctr-abc123', true);
      // No error = success
    });
  });

  describe('inspectContainer', () => {
    it('returns container info with mapped state', async () => {
      const info = await manager.inspectContainer('ctr-abc123');
      expect(info.containerId).toBe('ctr-abc123');
      expect(info.state).toBe('running');
      expect(info.port).toBe(32768);
    });
  });

  describe('getContainerLogs', () => {
    it('returns container logs', async () => {
      const logs = await manager.getContainerLogs('ctr-abc123', { tail: 50 });
      expect(logs.stdout).toBe('test log output');
    });
  });
});
