import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { api } from './api.js';

global.fetch = vi.fn();

function mockJsonResponse(body: unknown, init: Partial<Response> = {}): Response {
  return {
    ok: init.ok ?? true,
    status: init.status ?? 200,
    statusText: init.statusText ?? 'OK',
    json: vi.fn().mockResolvedValue(body),
  } as unknown as Response;
}

describe('API Client', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    window.localStorage.clear();
  });

  afterEach(() => {
    window.localStorage.clear();
  });

  describe('getServices', () => {
    it('fetches services successfully', async () => {
      const mockServices = [
        {
          id: 'test-service',
          name: 'Test Service',
          scriptPath: '../deploy-test.sh',
          healthUrl: 'https://test.example.com/health',
          repoPath: '../test-repo',
          status: 'up' as const,
          version: '1.0.0',
          productId: 'devops-internal',
        },
      ];

      vi.mocked(global.fetch).mockResolvedValueOnce(mockJsonResponse(mockServices));

      const services = await api.getServices();

      expect(services).toEqual(mockServices);
      expect(global.fetch).toHaveBeenCalledWith(
        'http://localhost:4004/api/services',
        expect.objectContaining({
          headers: expect.objectContaining({
            'Content-Type': 'application/json',
          }),
        })
      );
    });

    it('throws the normalized API error object on fetch failure', async () => {
      vi.mocked(global.fetch).mockResolvedValueOnce(mockJsonResponse({ error: 'boom' }, {
        ok: false,
        status: 500,
        statusText: 'Internal Server Error',
      }));

      await expect(api.getServices()).rejects.toMatchObject({
        error: 'API error: 500 Internal Server Error',
        status: 500,
      });
    });

    it('includes an auth token when available', async () => {
      window.localStorage.setItem('access_token', 'test-token');
      vi.mocked(global.fetch).mockResolvedValueOnce(mockJsonResponse([]));

      await api.getServices();

      expect(global.fetch).toHaveBeenCalledWith(
        'http://localhost:4004/api/services',
        expect.objectContaining({
          headers: expect.objectContaining({
            Authorization: 'Bearer test-token',
          }),
        })
      );
    });
  });

  describe('getHermesOps', () => {
    it('fetches the live Hermes operations snapshot', async () => {
      const snapshot = {
        generatedAt: '2026-05-27T13:03:14.848Z',
        tailscaleIp: '100.87.53.10',
        emergencyDriveUpload: {
          name: 'hermes-emergency-drive-upload.timer',
          active: true,
          nextRun: 'Thu 2026-05-28 03:26:15 UTC',
          lastRun: null,
        },
        instances: [],
        warnings: [],
      };

      vi.mocked(global.fetch).mockResolvedValueOnce(mockJsonResponse(snapshot));

      await expect(api.getHermesOps()).resolves.toEqual(snapshot);
      expect(global.fetch).toHaveBeenCalledWith(
        'http://localhost:4004/api/hermes/ops',
        expect.objectContaining({
          headers: expect.objectContaining({
            'Content-Type': 'application/json',
          }),
        }),
      );
    });
  });

  describe('state-changing requests', () => {
    it('triggers a deployment without CSRF when no user token exists', async () => {
      const mockResponse = {
        deploymentId: 'deployment-123',
        status: 'running',
      };

      vi.mocked(global.fetch).mockResolvedValueOnce(mockJsonResponse(mockResponse));

      const result = await api.triggerDeployment('test-service');

      expect(result).toEqual(mockResponse);
      expect(global.fetch).toHaveBeenCalledTimes(1);
      expect(global.fetch).toHaveBeenCalledWith(
        'http://localhost:4004/api/deployments/trigger/test-service',
        expect.objectContaining({
          method: 'POST',
        })
      );
    });

    it('fetches and attaches CSRF tokens for authenticated mutations', async () => {
      window.localStorage.setItem('access_token', 'test-token');
      vi.mocked(global.fetch)
        .mockResolvedValueOnce(mockJsonResponse({ csrfToken: 'csrf-token' }))
        .mockResolvedValueOnce(mockJsonResponse({ message: 'Seeded default services' }));

      const result = await api.seedServices();

      expect(result).toEqual({ message: 'Seeded default services' });
      expect(global.fetch).toHaveBeenNthCalledWith(
        1,
        'http://localhost:4004/api/csrf-token',
        expect.objectContaining({
          headers: expect.objectContaining({ Authorization: 'Bearer test-token' }),
        })
      );
      expect(global.fetch).toHaveBeenNthCalledWith(
        2,
        'http://localhost:4004/api/seed',
        expect.objectContaining({
          method: 'POST',
          headers: expect.objectContaining({
            Authorization: 'Bearer test-token',
            'X-CSRF-Token': 'csrf-token',
          }),
        })
      );
    });
  });
});
