/**
 * Integration tests for the gateway command system (P8-019)
 *
 * Covers:
 * - CommandRegistryService.getManifest() returns 12+ core commands
 * - All core commands have correct execution types
 * - Alias resolution works for all defined aliases
 * - CommandExecutorService routes known/unknown commands correctly
 * - /gc handler calls SessionGCService.sweepOrphans
 * - /system handler calls SystemOverrideService.set
 * - Unknown command returns descriptive error
 */
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { CommandRegistryService } from './command-registry.service.js';
import { CommandExecutorService } from './command-executor.service.js';
import type { SlashCommandPayload } from '@mosaicstack/types';

// ─── Mocks ───────────────────────────────────────────────────────────────────

const mockAgentService = {
  getSession: vi.fn(() => undefined),
};

const mockSystemOverride = {
  set: vi.fn().mockResolvedValue(undefined),
  get: vi.fn().mockResolvedValue(null),
  clear: vi.fn().mockResolvedValue(undefined),
  renew: vi.fn().mockResolvedValue(undefined),
};

const mockSessionGC = {
  sweepOrphans: vi.fn().mockResolvedValue({ orphanedSessions: 3, totalCleaned: [], duration: 12 }),
};

const mockRedis = {
  set: vi.fn().mockResolvedValue('OK'),
  get: vi.fn().mockResolvedValue(null),
  del: vi.fn().mockResolvedValue(0),
  keys: vi.fn().mockResolvedValue([]),
};

const mockBrain = {
  agents: {
    findByName: vi.fn().mockResolvedValue(undefined),
    findById: vi.fn().mockResolvedValue(undefined),
    create: vi.fn(),
  },
};

// ─── Helpers ─────────────────────────────────────────────────────────────────

function buildRegistry(): CommandRegistryService {
  const svc = new CommandRegistryService();
  svc.onModuleInit(); // seed core commands
  return svc;
}

function buildExecutor(registry: CommandRegistryService): CommandExecutorService {
  return new CommandExecutorService(
    registry as never,
    mockAgentService as never,
    mockSystemOverride as never,
    mockSessionGC as never,
    mockRedis as never,
    mockBrain as never,
    null, // reloadService (optional)
    null, // chatGateway (optional)
    null, // mcpClient (optional)
  );
}

// ─── Registry Tests ───────────────────────────────────────────────────────────

describe('CommandRegistryService — integration', () => {
  let registry: CommandRegistryService;

  beforeEach(() => {
    registry = buildRegistry();
  });

  it('getManifest() returns 12 or more core commands after onModuleInit', () => {
    const manifest = registry.getManifest();
    expect(manifest.commands.length).toBeGreaterThanOrEqual(12);
  });

  it('manifest version is 1', () => {
    expect(registry.getManifest().version).toBe(1);
  });

  it('manifest.skills is an array', () => {
    expect(Array.isArray(registry.getManifest().skills)).toBe(true);
  });

  it('all commands have required fields: name, description, execution, scope, available', () => {
    for (const cmd of registry.getManifest().commands) {
      expect(typeof cmd.name).toBe('string');
      expect(typeof cmd.description).toBe('string');
      expect(['local', 'socket', 'rest', 'hybrid']).toContain(cmd.execution);
      expect(['core', 'agent', 'admin']).toContain(cmd.scope);
      expect(typeof cmd.available).toBe('boolean');
    }
  });

  // Execution type verification for core commands
  const expectedExecutionTypes: Record<string, string> = {
    model: 'socket',
    thinking: 'socket',
    new: 'socket',
    clear: 'socket',
    compact: 'socket',
    retry: 'socket',
    rename: 'rest',
    history: 'rest',
    export: 'rest',
    preferences: 'rest',
    system: 'socket',
    help: 'local',
    gc: 'socket',
    agent: 'socket',
    provider: 'hybrid',
    mission: 'socket',
    prdy: 'socket',
    tools: 'socket',
    reload: 'socket',
  };

  for (const [name, expectedExecution] of Object.entries(expectedExecutionTypes)) {
    it(`command "${name}" has execution type "${expectedExecution}"`, () => {
      const cmd = registry.getManifest().commands.find((c) => c.name === name);
      expect(cmd, `command "${name}" not found`).toBeDefined();
      expect(cmd!.execution).toBe(expectedExecution);
    });
  }

  // Alias resolution checks
  const expectedAliases: Array<[string, string]> = [
    ['m', 'model'],
    ['t', 'thinking'],
    ['n', 'new'],
    ['a', 'agent'],
    ['s', 'status'],
    ['h', 'help'],
    ['pref', 'preferences'],
  ];

  for (const [alias, commandName] of expectedAliases) {
    it(`alias "/${alias}" resolves to command "${commandName}" via aliases array`, () => {
      const cmd = registry
        .getManifest()
        .commands.find((c) => c.name === commandName || c.aliases?.includes(alias));
      expect(cmd, `command with alias "${alias}" not found`).toBeDefined();
    });
  }
});

// ─── Executor Tests ───────────────────────────────────────────────────────────

describe('CommandExecutorService — integration', () => {
  let registry: CommandRegistryService;
  let executor: CommandExecutorService;
  const userId = 'user-integ-001';
  const userScope = { userId, tenantId: userId };
  const conversationId = 'conv-integ-001';

  beforeEach(() => {
    vi.clearAllMocks();
    registry = buildRegistry();
    executor = buildExecutor(registry);
  });

  // Unknown command returns error
  it('unknown command returns success:false with descriptive message', async () => {
    const payload: SlashCommandPayload = { command: 'nonexistent', conversationId };
    const result = await executor.execute(payload, userScope);
    expect(result.success).toBe(false);
    expect(result.message).toContain('nonexistent');
    expect(result.command).toBe('nonexistent');
  });

  it('/gc refuses an unaudited global sweep', async () => {
    const payload: SlashCommandPayload = { command: 'gc', conversationId };
    const result = await executor.execute(payload, userScope);
    expect(mockSessionGC.sweepOrphans).not.toHaveBeenCalled();
    expect(result.success).toBe(false);
    expect(result.message).toContain('disabled pending an authorized retention job');
  });

  // /system with args calls SystemOverrideService.set
  it('/system with text calls SystemOverrideService.set', async () => {
    const override = 'You are a helpful assistant.';
    const payload: SlashCommandPayload = { command: 'system', args: override, conversationId };
    const result = await executor.execute(payload, userScope);
    expect(mockSystemOverride.set).toHaveBeenCalledWith(conversationId, override, userScope);
    expect(result.success).toBe(true);
    expect(result.message).toContain('override set');
  });

  // /system with no args clears the override
  it('/system with no args calls SystemOverrideService.clear', async () => {
    const payload: SlashCommandPayload = { command: 'system', conversationId };
    const result = await executor.execute(payload, userScope);
    expect(mockSystemOverride.clear).toHaveBeenCalledWith(conversationId, userScope);
    expect(result.success).toBe(true);
    expect(result.message).toContain('cleared');
  });

  // /model with model name returns success
  it('/model with a model name returns success', async () => {
    const payload: SlashCommandPayload = {
      command: 'model',
      args: 'claude-3-opus',
      conversationId,
    };
    const result = await executor.execute(payload, userScope);
    expect(result.success).toBe(true);
    expect(result.command).toBe('model');
    expect(result.message).toContain('claude-3-opus');
  });

  // /thinking with valid level returns success
  it('/thinking with valid level returns success', async () => {
    const payload: SlashCommandPayload = { command: 'thinking', args: 'high', conversationId };
    const result = await executor.execute(payload, userScope);
    expect(result.success).toBe(true);
    expect(result.message).toContain('high');
  });

  // /thinking with invalid level returns usage message
  it('/thinking with invalid level returns usage message', async () => {
    const payload: SlashCommandPayload = { command: 'thinking', args: 'invalid', conversationId };
    const result = await executor.execute(payload, userScope);
    expect(result.success).toBe(true);
    expect(result.message).toContain('Usage:');
  });

  // /new command returns success
  it('/new returns success', async () => {
    const payload: SlashCommandPayload = { command: 'new', conversationId };
    const result = await executor.execute(payload, userScope);
    expect(result.success).toBe(true);
    expect(result.command).toBe('new');
  });

  // /reload without reloadService returns failure
  it('/reload without ReloadService returns failure', async () => {
    const payload: SlashCommandPayload = { command: 'reload', conversationId };
    const result = await executor.execute(payload, userScope);
    expect(result.success).toBe(false);
    expect(result.message).toContain('ReloadService');
  });

  // Commands not yet fully implemented return a fallback response
  const stubCommands = ['clear', 'compact', 'retry'];
  for (const cmd of stubCommands) {
    it(`/${cmd} returns success (stub)`, async () => {
      const payload: SlashCommandPayload = { command: cmd, conversationId };
      const result = await executor.execute(payload, userScope);
      expect(result.success).toBe(true);
      expect(result.command).toBe(cmd);
    });
  }
});
