import { describe, it, expect, vi, beforeAll, beforeEach } from 'vitest';
import { execFile } from 'child_process';

// Mock child_process before importing module
vi.mock('child_process', () => ({
  execFile: vi.fn(),
}));

const mockExecFile = vi.mocked(execFile);

describe('tailscale', () => {
  // Import after mocking
  let isTailscaleAvailable: typeof import('./tailscale.js').isTailscaleAvailable;
  let startTailscaleServe: typeof import('./tailscale.js').startTailscaleServe;
  let stopTailscaleServe: typeof import('./tailscale.js').stopTailscaleServe;

  beforeAll(async () => {
    const mod = await import('./tailscale.js');
    isTailscaleAvailable = mod.isTailscaleAvailable;
    startTailscaleServe = mod.startTailscaleServe;
    stopTailscaleServe = mod.stopTailscaleServe;
  });

  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('isTailscaleAvailable', () => {
    it('returns available when tailscale CLI works', async () => {
      mockExecFile
        .mockImplementationOnce((_cmd, _args, _opts, callback: any) => {
          callback(null, '1.62.0', '');
          return {} as any;
        })
        .mockImplementationOnce((_cmd, _args, _opts, callback: any) => {
          callback(null, '{}', '');
          return {} as any;
        });

      const result = await isTailscaleAvailable();
      expect(result.available).toBe(true);
      expect(result.version).toBe('1.62.0');
    });

    it('returns unavailable when tailscale CLI fails', async () => {
      mockExecFile.mockImplementationOnce((_cmd, _args, _opts, callback: any) => {
        callback(new Error('command not found'), '', 'command not found');
        return {} as any;
      });

      const result = await isTailscaleAvailable();
      expect(result.available).toBe(false);
      expect(result.error).toBeDefined();
    });
  });

  describe('startTailscaleServe', () => {
    it('calls tailscale serve with correct args', async () => {
      mockExecFile
        // serve command
        .mockImplementationOnce((_cmd, _args, _opts, callback: any) => {
          callback(null, '', '');
          return {} as any;
        })
        // status for hostname
        .mockImplementationOnce((_cmd, _args, _opts, callback: any) => {
          callback(null, JSON.stringify({ Self: { DNSName: 'myhost.tailnet.ts.net.' } }), '');
          return {} as any;
        });

      const url = await startTailscaleServe({ localPort: 18800 });
      expect(url).toBe('https://myhost.tailnet.ts.net');

      const serveCall = mockExecFile.mock.calls[0];
      expect(serveCall[0]).toBe('tailscale');
      expect(serveCall[1]).toEqual(['serve', '--bg', '--https=443', 'http://127.0.0.1:18800']);
    });

    it('uses custom serve port', async () => {
      mockExecFile
        .mockImplementationOnce((_cmd, _args, _opts, callback: any) => {
          callback(null, '', '');
          return {} as any;
        })
        .mockImplementationOnce((_cmd, _args, _opts, callback: any) => {
          callback(null, JSON.stringify({ Self: { DNSName: 'myhost.tailnet.ts.net.' } }), '');
          return {} as any;
        });

      const url = await startTailscaleServe({ localPort: 18800, servePort: 8443 });
      expect(url).toBe('https://myhost.tailnet.ts.net:8443');

      const serveCall = mockExecFile.mock.calls[0];
      expect(serveCall[1]).toEqual(['serve', '--bg', '--https=8443', 'http://127.0.0.1:18800']);
    });
  });

  describe('stopTailscaleServe', () => {
    it('calls tailscale serve off', async () => {
      mockExecFile.mockImplementationOnce((_cmd, _args, _opts, callback: any) => {
        callback(null, '', '');
        return {} as any;
      });

      await stopTailscaleServe({ localPort: 18800 });

      expect(mockExecFile).toHaveBeenCalledOnce();
      const call = mockExecFile.mock.calls[0];
      expect(call[0]).toBe('tailscale');
      expect(call[1]).toEqual(['serve', '--https=443', 'off']);
    });

    it('does not throw on failure', async () => {
      mockExecFile.mockImplementationOnce((_cmd, _args, _opts, callback: any) => {
        callback(new Error('failed'), '', 'failed');
        return {} as any;
      });

      // Should not throw
      await stopTailscaleServe({ localPort: 18800 });
    });
  });
});
