import { describe, it, expect, vi, beforeEach } from 'vitest';
import { McpServerService, NotFoundError, ConflictError } from '../src/services/mcp-server.service.js';
import type { IMcpServerRepository } from '../src/repositories/interfaces.js';

function mockRepo(): IMcpServerRepository {
  return {
    findAll: vi.fn(async () => []),
    findById: vi.fn(async () => null),
    findByName: vi.fn(async () => null),
    create: vi.fn(async (data) => ({
      id: 'new-id',
      name: data.name,
      description: data.description ?? '',
      packageName: data.packageName ?? null,
      dockerImage: null,
      transport: data.transport ?? 'STDIO',
      repositoryUrl: data.repositoryUrl ?? null,
      env: data.env ?? [],
      version: 1,
      createdAt: new Date(),
      updatedAt: new Date(),
    })),
    update: vi.fn(async (id, data) => ({
      id,
      name: 'test',
      description: (data.description as string) ?? '',
      packageName: null,
      dockerImage: null,
      transport: 'STDIO' as const,
      repositoryUrl: null,
      env: [],
      version: 2,
      createdAt: new Date(),
      updatedAt: new Date(),
    })),
    delete: vi.fn(async () => {}),
  };
}

describe('McpServerService', () => {
  let repo: ReturnType<typeof mockRepo>;
  let service: McpServerService;

  beforeEach(() => {
    repo = mockRepo();
    service = new McpServerService(repo);
  });

  describe('list', () => {
    it('returns all servers', async () => {
      const servers = await service.list();
      expect(repo.findAll).toHaveBeenCalled();
      expect(servers).toEqual([]);
    });
  });

  describe('getById', () => {
    it('returns server when found', async () => {
      const server = { id: '1', name: 'test' };
      vi.mocked(repo.findById).mockResolvedValue(server as never);
      const result = await service.getById('1');
      expect(result.id).toBe('1');
    });

    it('throws NotFoundError when not found', async () => {
      await expect(service.getById('missing')).rejects.toThrow(NotFoundError);
    });
  });

  describe('create', () => {
    it('creates a server with valid input', async () => {
      const result = await service.create({ name: 'my-server' });
      expect(result.name).toBe('my-server');
      expect(repo.create).toHaveBeenCalled();
    });

    it('throws ConflictError when name exists', async () => {
      vi.mocked(repo.findByName).mockResolvedValue({ id: '1', name: 'existing' } as never);
      await expect(service.create({ name: 'existing' })).rejects.toThrow(ConflictError);
    });

    it('throws on invalid input', async () => {
      await expect(service.create({ name: '' })).rejects.toThrow();
    });
  });

  describe('update', () => {
    it('updates an existing server', async () => {
      vi.mocked(repo.findById).mockResolvedValue({ id: '1', name: 'test' } as never);
      await service.update('1', { description: 'updated' });
      expect(repo.update).toHaveBeenCalledWith('1', { description: 'updated' });
    });

    it('throws NotFoundError when server does not exist', async () => {
      await expect(service.update('missing', {})).rejects.toThrow(NotFoundError);
    });
  });

  describe('delete', () => {
    it('deletes an existing server', async () => {
      vi.mocked(repo.findById).mockResolvedValue({ id: '1', name: 'test' } as never);
      await service.delete('1');
      expect(repo.delete).toHaveBeenCalledWith('1');
    });

    it('throws NotFoundError when server does not exist', async () => {
      await expect(service.delete('missing')).rejects.toThrow(NotFoundError);
    });
  });
});
