// src/routes/system.routes.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import supertest from 'supertest';
import systemRouter from './system.routes'; // This was a duplicate, fixed.
import { exec, type ExecException, type ExecOptions } from 'child_process';
import { geocodingService } from '../services/geocodingService.server';
import { createTestApp } from '../tests/utils/createTestApp';

// FIX: Use the simple factory pattern for child_process to avoid default export issues
vi.mock('child_process', () => {
  const mockExec = vi.fn((command, callback) => {
    if (typeof callback === 'function') {
      callback(null, 'PM2 OK', '');
    }
    return { unref: () => {} };
  });

  return {
    default: { exec: mockExec },
    exec: mockExec
  };
});

// 2. Mock Geocoding
vi.mock('../services/geocodingService.server', () => ({
  geocodingService: {
    geocodeAddress: vi.fn(),
  },
}));

// 3. Mock Logger
vi.mock('../services/logger.server', () => ({
  logger: {
    info: vi.fn(),
    debug: vi.fn(),
    error: vi.fn(),
    warn: vi.fn(),
    child: vi.fn().mockReturnThis(),
  },
}));

describe('System Routes (/api/system)', () => {
  const app = createTestApp({ router: systemRouter, basePath: '/api/system' });
  beforeEach(() => {
    // We cast here to get type-safe access to mock functions like .mockImplementation
    vi.clearAllMocks();
  });

  describe('GET /pm2-status', () => {
    it('should return success: true when pm2 process is online', async () => {
      // Arrange: Simulate a successful `pm2 describe` output for an online process.
      const pm2OnlineOutput = `
        ┌─ PM2 info ────────────────┐
        │ status    │ online    │
        └───────────┴───────────┘
      `;
      
      type ExecCallback = (error: ExecException | null, stdout: string, stderr: string) => void;

      // A robust mock for `exec` that handles its multiple overloads.
      // This avoids the complex and error-prone `...args` signature.
      vi.mocked(exec).mockImplementation((
        command: string,
        options?: ExecOptions | ExecCallback | null,
        callback?: ExecCallback | null
      ) => {
        // The actual callback can be the second or third argument.
        const actualCallback = (typeof options === 'function' ? options : callback) as ExecCallback;
        if (actualCallback) {
          actualCallback(null, pm2OnlineOutput, '');
        }
        // Return a minimal object that satisfies the ChildProcess type for .unref()
        return { unref: () => {} } as ReturnType<typeof exec>;
      });

      // Act
      const response = await supertest(app).get('/api/system/pm2-status');

      // Assert
      expect(response.status).toBe(200);
      expect(response.body).toEqual({ success: true, message: 'Application is online and running under PM2.' });
    });

    it('should return success: false when pm2 process is stopped or errored', async () => {
      const pm2StoppedOutput = `│ status    │ stopped   │`;
      
      vi.mocked(exec).mockImplementation((
        command: string,
        options?: ExecOptions | ((error: ExecException | null, stdout: string, stderr: string) => void) | null,
        callback?: ((error: ExecException | null, stdout: string, stderr: string) => void) | null
      ) => {
        const actualCallback = (typeof options === 'function' ? options : callback) as ((error: ExecException | null, stdout: string, stderr: string) => void);
        if (actualCallback) {
          actualCallback(null, pm2StoppedOutput, '');
        }
        return { unref: () => {} } as ReturnType<typeof exec>;
      });

      const response = await supertest(app).get('/api/system/pm2-status');

      // Assert
      expect(response.status).toBe(200);
      expect(response.body.success).toBe(false);
    });

    it('should return 500 on a generic exec error', async () => {
      vi.mocked(exec).mockImplementation((
        command: string,
        options?: ExecOptions | ((error: ExecException | null, stdout: string, stderr: string) => void) | null,
        callback?: ((error: ExecException | null, stdout: string, stderr: string) => void) | null
      ) => {
        const actualCallback = (typeof options === 'function' ? options : callback) as ((error: ExecException | null, stdout: string, stderr: string) => void);
        if (actualCallback) {
          actualCallback(new Error('System error') as ExecException, '', 'stderr output');
        }
        return { unref: () => {} } as ReturnType<typeof exec>;
      });

      // Act
      const response = await supertest(app).get('/api/system/pm2-status');

      // Assert
      expect(response.status).toBe(500);
      expect(response.body.message).toBe('System error');
    });
  });

  describe('POST /geocode', () => {
    it('should return geocoded coordinates for a valid address', async () => {
      // Arrange
      const mockCoordinates = { lat: 48.4284, lng: -123.3656 };
      vi.mocked(geocodingService.geocodeAddress).mockResolvedValue(mockCoordinates);

      // Act
      const response = await supertest(app)
        .post('/api/system/geocode')
        .send({ address: 'Victoria, BC' });

      // Assert
      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockCoordinates);
    });

    it('should return 404 if the address cannot be geocoded', async () => {
      vi.mocked(geocodingService.geocodeAddress).mockResolvedValue(null);
      const response = await supertest(app)
        .post('/api/system/geocode')
        .send({ address: 'Invalid Address' });
      expect(response.status).toBe(404);
      expect(response.body.message).toBe('Could not geocode the provided address.');
    });

    it('should return 500 if the geocoding service throws an error', async () => {
      const geocodeError = new Error('Geocoding service unavailable');
      vi.mocked(geocodingService.geocodeAddress).mockRejectedValue(geocodeError);
      const response = await supertest(app).post('/api/system/geocode').send({ address: 'Any Address' });
      expect(response.status).toBe(500);
    });

    it('should return 400 if the address is missing from the body', async () => {
      const response = await supertest(app)
        .post('/api/system/geocode')
        .send({ not_address: 'Victoria, BC' });
      expect(response.status).toBe(400);
      // Zod validation error message can vary slightly depending on configuration or version
      expect(response.body.errors[0].message).toMatch(/An address string is required|Required/i);
    });
  });
});