import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
  analyzeFleetRequest,
  dispatchFleetAction,
  type FleetDevice,
} from '@/lib/token/agents/fleet-manager';

// Mock logger
vi.mock('@/lib/logger', () => ({
  logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn() },
}));

describe('analyzeFleetRequest', () => {
  const originalEnv = process.env.OPENROUTER_API_KEY;

  beforeEach(() => {
    vi.clearAllMocks();
    process.env.OPENROUTER_API_KEY = 'test-openrouter-key';
  });

  afterEach(() => {
    vi.restoreAllMocks();
    if (originalEnv !== undefined) {
      process.env.OPENROUTER_API_KEY = originalEnv;
    } else {
      delete process.env.OPENROUTER_API_KEY;
    }
  });

  const testDevices: FleetDevice[] = [
    {
      id: 'dev_1',
      name: 'Desktop',
      status: 'online',
      models: ['qwen3:8b'],
      hardware: 'RTX 4080 16GB',
      tunnelUrl: 'http://desktop.local:11434',
    },
  ];

  function mockFetchResponse(
    content: string,
    options: { ok?: boolean; status?: number; costHeader?: string } = {},
  ) {
    const { ok = true, status = 200, costHeader = '0.002' } = options;
    const mockResponse = {
      ok,
      status,
      json: async () => ({
        choices: [{ message: { content } }],
        usage: { prompt_tokens: 150, completion_tokens: 100 },
        model: 'google/gemini-2.5-flash',
      }),
      text: async () => content,
      headers: new Headers({
        ...(costHeader ? { 'x-openrouter-cost': costHeader } : {}),
      }),
    };
    vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse));
  }

  it('should propose actions from structured LLM response', async () => {
    // Arrange
    const llmResponse = JSON.stringify({
      analysis: 'Installing qwen3:14b on Desktop — fits within 16GB VRAM.',
      actions: [
        {
          deviceId: 'dev_1',
          deviceName: 'Desktop',
          type: 'pull_model',
          payload: { model: 'qwen3:14b' },
          reasoning: 'Desktop has 16GB VRAM, enough for qwen3:14b (9.3 GB)',
        },
      ],
    });
    mockFetchResponse(llmResponse);

    // Act
    const result = await analyzeFleetRequest(
      'install qwen3:14b on my desktop',
      'google/gemini-2.5-flash',
      testDevices,
    );

    // Assert
    expect(result.analysis).toBe('Installing qwen3:14b on Desktop — fits within 16GB VRAM.');
    expect(result.actions).toHaveLength(1);
    expect(result.actions[0].type).toBe('pull_model');
    expect(result.actions[0].deviceId).toBe('dev_1');
    expect(result.actions[0].payload.model).toBe('qwen3:14b');
  });

  it('should handle empty actions array', async () => {
    // Arrange
    const llmResponse = JSON.stringify({
      analysis: 'No valid actions could be derived from the request.',
      actions: [],
    });
    mockFetchResponse(llmResponse);

    // Act
    const result = await analyzeFleetRequest(
      'do something vague',
      'google/gemini-2.5-flash',
      testDevices,
    );

    // Assert
    expect(result.actions).toHaveLength(0);
    expect(result.analysis).toContain('No valid actions');
  });

  it('should limit to 5 actions maximum', async () => {
    // Arrange
    const tenActions = Array.from({ length: 10 }, (_, i) => ({
      deviceId: 'dev_1',
      deviceName: 'Desktop',
      type: 'pull_model',
      payload: { model: `model_${i}` },
      reasoning: `Action ${i}`,
    }));
    mockFetchResponse(JSON.stringify({ analysis: 'Many actions', actions: tenActions }));

    // Act
    const result = await analyzeFleetRequest(
      'install everything',
      'google/gemini-2.5-flash',
      testDevices,
    );

    // Assert
    expect(result.actions.length).toBeLessThanOrEqual(5);
  });

  it('should filter invalid action types', async () => {
    // Arrange
    const llmResponse = JSON.stringify({
      analysis: 'Some actions',
      actions: [
        {
          deviceId: 'dev_1',
          deviceName: 'Desktop',
          type: 'invalid_type',
          payload: {},
          reasoning: 'Bad type',
        },
        {
          deviceId: 'dev_1',
          deviceName: 'Desktop',
          type: 'pull_model',
          payload: { model: 'qwen3:8b' },
          reasoning: 'Good type',
        },
      ],
    });
    mockFetchResponse(llmResponse);

    // Act
    const result = await analyzeFleetRequest(
      'install stuff',
      'google/gemini-2.5-flash',
      testDevices,
    );

    // Assert
    expect(result.actions).toHaveLength(1);
    expect(result.actions[0].type).toBe('pull_model');
  });

  it('should build human-readable content with action list', async () => {
    // Arrange
    const llmResponse = JSON.stringify({
      analysis: 'Installing qwen3:14b on Desktop.',
      actions: [
        {
          deviceId: 'dev_1',
          deviceName: 'Desktop',
          type: 'pull_model',
          payload: { model: 'qwen3:14b' },
          reasoning: 'Fits 16GB VRAM',
        },
      ],
    });
    mockFetchResponse(llmResponse);

    // Act
    const result = await analyzeFleetRequest(
      'install qwen3:14b',
      'google/gemini-2.5-flash',
      testDevices,
    );

    // Assert
    expect(result.content).toContain('Proposed Actions');
    expect(result.content).toContain('Install');
    expect(result.content).toContain('qwen3:14b');
    expect(result.content).toContain('Desktop');
    expect(result.content).toContain('Approve');
  });

  it('should throw on missing API key', async () => {
    // Arrange
    delete process.env.OPENROUTER_API_KEY;

    // Act & Assert
    await expect(
      analyzeFleetRequest('install qwen3:8b', 'google/gemini-2.5-flash', testDevices),
    ).rejects.toThrow('OPENROUTER_API_KEY not configured');
  });

  it('should throw on API error', async () => {
    // Arrange
    mockFetchResponse('Server error', { ok: false, status: 500 });

    // Act & Assert
    await expect(
      analyzeFleetRequest('install qwen3:8b', 'google/gemini-2.5-flash', testDevices),
    ).rejects.toThrow('Fleet manager API error (500)');
  });

  it('should handle malformed JSON gracefully with invalid braces', async () => {
    // Arrange — response has braces but invalid JSON, triggering catch block
    mockFetchResponse('Here is my analysis {invalid json: not really json!!!}');

    // Act
    const result = await analyzeFleetRequest(
      'install stuff',
      'google/gemini-2.5-flash',
      testDevices,
    );

    // Assert — catch sets analysis = rawContent, actions empty
    expect(result.actions).toHaveLength(0);
    expect(result.analysis).toContain('Here is my analysis');
  });

  it('should return empty analysis when no JSON found in response', async () => {
    // Arrange — no braces at all, jsonMatch is null
    mockFetchResponse('This is just plain text with no JSON structure.');

    // Act
    const result = await analyzeFleetRequest(
      'install stuff',
      'google/gemini-2.5-flash',
      testDevices,
    );

    // Assert — no match, analysis stays at initial ""
    expect(result.actions).toHaveLength(0);
    expect(result.analysis).toBe('');
  });
});

describe('dispatchFleetAction', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should call correct Ollama endpoint for pull_model', async () => {
    // Arrange
    const mockFetch = vi.fn().mockResolvedValue({
      ok: true,
      text: async () => 'OK',
    });
    vi.stubGlobal('fetch', mockFetch);

    // Act
    const result = await dispatchFleetAction(
      'http://desktop.local:11434',
      'pull_model',
      { model: 'qwen3:8b' },
    );

    // Assert
    expect(result.success).toBe(true);
    expect(mockFetch).toHaveBeenCalledWith(
      'http://desktop.local:11434/api/pull',
      expect.objectContaining({
        method: 'POST',
        body: JSON.stringify({ name: 'qwen3:8b' }),
      }),
    );
  });

  it('should call correct Ollama endpoint for remove_model', async () => {
    // Arrange
    const mockFetch = vi.fn().mockResolvedValue({
      ok: true,
      text: async () => 'OK',
    });
    vi.stubGlobal('fetch', mockFetch);

    // Act
    const result = await dispatchFleetAction(
      'http://desktop.local:11434',
      'remove_model',
      { model: 'llama3:8b' },
    );

    // Assert
    expect(result.success).toBe(true);
    expect(mockFetch).toHaveBeenCalledWith(
      'http://desktop.local:11434/api/delete',
      expect.objectContaining({
        method: 'POST',
        body: JSON.stringify({ name: 'llama3:8b' }),
      }),
    );
  });

  it('should call health endpoint for restart_engine', async () => {
    // Arrange
    const mockFetch = vi.fn().mockResolvedValue({
      ok: true,
      text: async () => 'Restarted',
    });
    vi.stubGlobal('fetch', mockFetch);

    // Act
    const result = await dispatchFleetAction(
      'http://desktop.local:11434',
      'restart_engine',
      {},
    );

    // Assert
    expect(result.success).toBe(true);
    expect(mockFetch).toHaveBeenCalledWith(
      'http://desktop.local:11434/v1/health',
      expect.objectContaining({
        method: 'POST',
        body: JSON.stringify({ action: 'restart' }),
      }),
    );
  });

  it('should return failure for unknown action type', async () => {
    // Act
    const result = await dispatchFleetAction(
      'http://desktop.local:11434',
      'unknown_action',
      {},
    );

    // Assert
    expect(result.success).toBe(false);
    expect(result.result).toContain('Unknown action type');
  });

  it('should handle fetch error gracefully', async () => {
    // Arrange
    vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('Connection refused')));

    // Act
    const result = await dispatchFleetAction(
      'http://desktop.local:11434',
      'pull_model',
      { model: 'qwen3:8b' },
    );

    // Assert
    expect(result.success).toBe(false);
    expect(result.result).toContain('Dispatch failed');
    expect(result.result).toContain('Connection refused');
  });

  it('should handle non-ok response from device', async () => {
    // Arrange
    const mockResponse = {
      ok: false,
      status: 500,
      text: async () => 'Internal Ollama Error',
    };
    vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse));

    // Act
    const result = await dispatchFleetAction(
      'http://desktop.local:11434',
      'pull_model',
      { model: 'qwen3:8b' },
    );

    // Assert
    expect(result.success).toBe(false);
    expect(result.result).toContain('500');
    expect(result.result).toContain('Internal Ollama Error');
  });
});
