import { describe, it, expect, beforeEach, vi } from 'vitest';
import type { Service } from './types.js';

vi.mock('../../lib/config.js', () => ({
  productId: 'devops-internal',
}));

const mockContainer = vi.hoisted(() => ({
  items: {
    create: vi.fn(),
    query: vi.fn(),
  },
  item: vi.fn(),
}));

vi.mock('../../lib/cosmos-init.js', () => ({
  getContainer: vi.fn(() => mockContainer),
}));

const { createService, getServiceById, getAllServices, updateService, deleteService } = await import('./repository.js');

describe('Services Repository', () => {
  const existingService: Service = {
    id: 'test-service',
    name: 'Test Service',
    scriptPath: '../deploy-test.sh',
    healthUrl: 'https://test.example.com/health',
    repoPath: '../test-repo',
    status: 'up',
    version: '1.0.0',
    productId: 'devops-internal',
  };

  beforeEach(() => {
    vi.clearAllMocks();
    mockContainer.items.create.mockImplementation(async (service: Service) => ({ resource: service }));
    mockContainer.items.query.mockReturnValue({
      fetchAll: vi.fn().mockResolvedValue({ resources: [existingService] }),
    });
    mockContainer.item.mockImplementation((id: string) => ({
      read: vi.fn().mockImplementation(async () => {
        if (id === existingService.id) return { resource: existingService };
        throw new Error('Not found');
      }),
      replace: vi.fn().mockImplementation(async (updated: Service) => ({ resource: updated })),
      delete: vi.fn().mockImplementation(async () => {
        if (id === existingService.id) return {};
        throw new Error('Not found');
      }),
    }));
  });

  describe('createService', () => {
    it('creates a service with operational defaults and persists it', async () => {
      const serviceData = {
        id: 'test-service',
        name: 'Test Service',
        scriptPath: '../deploy-test.sh',
        healthUrl: 'https://test.example.com/health',
        repoPath: '../test-repo',
      };

      const service = await createService(serviceData);

      expect(service).toMatchObject({
        ...serviceData,
        status: 'down',
        version: 'unknown',
        productId: 'devops-internal',
      });
      expect(mockContainer.items.create).toHaveBeenCalledWith(expect.objectContaining(serviceData));
    });

    it('generates an id when one is not provided', async () => {
      const randomUUID = vi.spyOn(crypto, 'randomUUID').mockReturnValue('00000000-0000-4000-8000-000000000001');

      const service = await createService({
        name: 'Generated Service',
        scriptPath: '../deploy-generated.sh',
        healthUrl: 'https://generated.example.com/health',
        repoPath: '../generated-repo',
      });

      expect(service.id).toBe('00000000-0000-4000-8000-000000000001');
      randomUUID.mockRestore();
    });
  });

  describe('getServiceById', () => {
    it('retrieves a service by id and partition key', async () => {
      const service = await getServiceById('test-service');

      expect(service).toEqual(existingService);
      expect(mockContainer.item).toHaveBeenCalledWith('test-service', 'test-service');
    });

    it('returns null for a missing service', async () => {
      await expect(getServiceById('non-existent')).resolves.toBeNull();
    });
  });

  describe('getAllServices', () => {
    it('queries only services for the dashboard product', async () => {
      const services = await getAllServices();

      expect(services).toEqual([existingService]);
      expect(mockContainer.items.query).toHaveBeenCalledWith({
        query: 'SELECT * FROM c WHERE c.productId = @productId',
        parameters: [{ name: '@productId', value: 'devops-internal' }],
      });
    });
  });

  describe('updateService', () => {
    it('merges updates into an existing service', async () => {
      const service = await updateService('test-service', { name: 'Updated Service Name' });

      expect(service).toMatchObject({
        ...existingService,
        name: 'Updated Service Name',
      });
    });

    it('returns null when updating a missing service', async () => {
      await expect(updateService('missing', { name: 'Nope' })).resolves.toBeNull();
    });
  });

  describe('deleteService', () => {
    it('returns true after deleting an existing service', async () => {
      await expect(deleteService('test-service')).resolves.toBe(true);
    });

    it('returns false when deleting a missing service', async () => {
      await expect(deleteService('missing')).resolves.toBe(false);
    });
  });
});
