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

vi.mock('electron', () => ({
  ipcMain: { handle: vi.fn() },
  dialog: { showOpenDialog: vi.fn() },
  BrowserWindow: { fromWebContents: vi.fn() },
}));

vi.mock('@chamber/services', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@chamber/services')>();
  return {
    ...actual,
    bootstrapMindCapabilities: vi.fn(),
  };
});

import { ipcMain, BrowserWindow } from 'electron';
import type { IpcMainInvokeEvent } from 'electron';
import { setupGenesisIPC } from './genesis';
import { bootstrapMindCapabilities, type GenesisMindTemplate, type MindManager, type MindScaffold } from '@chamber/services';

type InvokeHandler = (event: IpcMainInvokeEvent, ...args: unknown[]) => unknown;

const EVT = { sender: {} } as IpcMainInvokeEvent;

const lucyTemplate: GenesisMindTemplate = {
  id: 'lucy',
  displayName: 'Lucy',
  description: 'A calm Chief of Staff mind.',
  role: 'Chief of Staff',
  voice: 'Vanilla, calm, helpful, and precise',
  templateVersion: '0.1.0',
  agent: '.github/agents/lucy.agent.md',
  requiredFiles: ['SOUL.md'],
  source: {
    owner: 'ianphil',
    repo: 'genesis-minds',
    ref: 'master',
    plugin: 'genesis-minds',
    manifestPath: 'plugins/genesis-minds/minds/lucy/mind.json',
    rootPath: 'plugins/genesis-minds/minds/lucy',
  },
};

describe('setupGenesisIPC', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(BrowserWindow.fromWebContents).mockReturnValue({ webContents: { send: vi.fn() } } as never);
  });

  it('registers template listing and install handlers', () => {
    setupGenesisIPC(createMindManager(), createScaffold(), createCatalog(), createInstaller());

    const channels = vi.mocked(ipcMain.handle).mock.calls.map((call) => call[0]);

    expect(channels).toContain('genesis:listTemplates');
    expect(channels).toContain('genesis:createFromTemplate');
  });

  it('lists predefined templates from the catalog', async () => {
    const catalog = createCatalog();
    setupGenesisIPC(createMindManager(), createScaffold(), catalog, createInstaller());

    await expect(getHandler('genesis:listTemplates')(EVT)).resolves.toEqual([lucyTemplate]);
    expect(catalog.listTemplates).toHaveBeenCalled();
  });

  it('installs a predefined template and activates the loaded mind', async () => {
    const mindManager = createMindManager();
    const installer = createInstaller();
    const mockSend = vi.fn();
    vi.mocked(BrowserWindow.fromWebContents).mockReturnValue({ webContents: { send: mockSend } } as never);
    setupGenesisIPC(mindManager, createScaffold(), createCatalog(), installer);

    await expect(getHandler('genesis:createFromTemplate')(EVT, { templateId: 'lucy', basePath: 'C:\\agents' })).resolves.toEqual({
      success: true,
      mindId: 'lucy-1234',
      mindPath: 'C:\\agents\\lucy',
    });

    expect(installer.install).toHaveBeenCalledWith({ templateId: 'lucy', basePath: 'C:\\agents' });
    expect(bootstrapMindCapabilities).toHaveBeenCalledWith('C:\\agents\\lucy');
    expect(mindManager.loadMind).toHaveBeenCalledWith('C:\\agents\\lucy', undefined, { enforceUnique: true });
    expect(mindManager.setActiveMind).toHaveBeenCalledWith('lucy-1234');
    expect(mockSend).toHaveBeenCalledWith('genesis:progress', { step: 'complete', detail: 'Genesis template install complete.' });
  });

  it('returns a clear error when predefined template install fails without generating a custom mind', async () => {
    const scaffold = createScaffold();
    const installer = createInstaller();
    installer.install.mockRejectedValue(new Error('marketplace unavailable'));
    setupGenesisIPC(createMindManager(), scaffold, createCatalog(), installer);

    await expect(getHandler('genesis:createFromTemplate')(EVT, { templateId: 'lucy', basePath: 'C:\\agents' })).resolves.toEqual({
      success: false,
      error: 'marketplace unavailable',
    });

    expect(scaffold.create).not.toHaveBeenCalled();
  });

  describe('genesis:createFromTemplate input validation', () => {
    const invalidPayloads: Array<[string, unknown]> = [
      ['missing templateId', { basePath: 'C:\\agents' }],
      ['empty templateId', { templateId: '', basePath: 'C:\\agents' }],
      ['non-string templateId', { templateId: 123, basePath: 'C:\\agents' }],
      ['missing basePath', { templateId: 'lucy' }],
      ['empty basePath', { templateId: 'lucy', basePath: '' }],
      ['non-string basePath', { templateId: 'lucy', basePath: { dir: 'C:\\agents' } }],
      ['null payload', null],
      ['array payload', ['lucy', 'C:\\agents']],
      ['unknown extra field', { templateId: 'lucy', basePath: 'C:\\agents', shellCommand: 'rm -rf /' }],
    ];

    for (const [label, value] of invalidPayloads) {
      it(`rejects ${label} without invoking the installer`, async () => {
        const installer = createInstaller();
        setupGenesisIPC(createMindManager(), createScaffold(), createCatalog(), installer);

        await expect(getHandler('genesis:createFromTemplate')(EVT, value)).rejects.toThrow(TypeError);
        expect(installer.install).not.toHaveBeenCalled();
      });
    }

    it('TypeError message names the channel and the bad field', async () => {
      setupGenesisIPC(createMindManager(), createScaffold(), createCatalog(), createInstaller());

      await expect(
        getHandler('genesis:createFromTemplate')(EVT, { templateId: '', basePath: 'C:\\agents' }),
      ).rejects.toThrow(/genesis:createFromTemplate/);
      await expect(
        getHandler('genesis:createFromTemplate')(EVT, { templateId: '', basePath: 'C:\\agents' }),
      ).rejects.toThrow(/templateId/);
    });
  });

  describe('genesis:create duplicate-name pre-check (#44)', () => {
    it('rejects with a friendly error and does not call scaffold.create when a mind with that name is already loaded', async () => {
      const scaffold = createScaffold();
      const mindManager = createMindManager({
        findByName: vi.fn((name: string) =>
          name.toLowerCase() === 'alfred' ? { mindId: 'alfred-1234', identity: { name: 'Alfred' } } : undefined,
        ),
      });
      const mockSend = vi.fn();
      vi.mocked(BrowserWindow.fromWebContents).mockReturnValue({ webContents: { send: mockSend } } as never);
      setupGenesisIPC(mindManager, scaffold, createCatalog(), createInstaller());

      const result = await getHandler('genesis:create')(EVT, {
        name: 'Alfred',
        role: 'butler',
        voice: 'plain',
        voiceDescription: 'plain',
        basePath: 'C:\\agents',
      });

      expect(result).toEqual({
        success: false,
        error: expect.stringMatching(/already exists/i),
      });
      expect(scaffold.create).not.toHaveBeenCalled();
      expect(mindManager.findByName).toHaveBeenCalledWith('Alfred');
      expect(mockSend).toHaveBeenCalledWith(
        'genesis:progress',
        expect.objectContaining({ step: 'error', detail: expect.stringMatching(/already exists/i) }),
      );
    });

    it('proceeds with scaffold.create when no name collision exists', async () => {
      const scaffold = createScaffold();
      scaffold.create.mockResolvedValue('C:\\agents\\alfred');
      const mindManager = createMindManager(); // findByName defaults to () => undefined
      setupGenesisIPC(mindManager, scaffold, createCatalog(), createInstaller());

      await getHandler('genesis:create')(EVT, {
        name: 'Alfred',
        role: 'butler',
        voice: 'plain',
        voiceDescription: 'plain',
        basePath: 'C:\\agents',
      });

      expect(mindManager.findByName).toHaveBeenCalledWith('Alfred');
      expect(scaffold.create).toHaveBeenCalled();
      expect(mindManager.loadMind).toHaveBeenCalledWith(
        'C:\\agents\\alfred',
        undefined,
        { enforceUnique: true },
      );
    });

    it('activateCreatedMind passes enforceUnique:true so a TOCTOU collision after pre-check is also caught', async () => {
      // Pre-check passes (findByName returns undefined), but a concurrent
      // mind:add or template install lands a colliding mind during the
      // scaffold I/O window. loadMind throws "already exists"; the IPC
      // handler must surface the error rather than registering the mind.
      const scaffold = createScaffold();
      scaffold.create.mockResolvedValue('C:\\agents\\alfred');
      const mindManager = createMindManager();
      mindManager.loadMind.mockRejectedValueOnce(
        new Error('An agent named "Alfred" already exists. Choose a different name.'),
      );
      setupGenesisIPC(mindManager, scaffold, createCatalog(), createInstaller());

      const result = await getHandler('genesis:create')(EVT, {
        name: 'Alfred',
        role: 'butler',
        voice: 'plain',
        voiceDescription: 'plain',
        basePath: 'C:\\agents',
      });

      expect(result).toEqual({
        success: false,
        error: expect.stringMatching(/already exists/i),
      });
      expect(mindManager.setActiveMind).not.toHaveBeenCalled();
    });
  });
});

function getHandler(name: string): InvokeHandler {
  const call = vi.mocked(ipcMain.handle).mock.calls.find((item) => item[0] === name);
  if (!call) throw new Error(`no handler registered for ${name}`);
  return call[1] as InvokeHandler;
}

function createMindManager(overrides?: {
  findByName?: ReturnType<typeof vi.fn>;
}): MindManager & {
  loadMind: ReturnType<typeof vi.fn>;
  setActiveMind: ReturnType<typeof vi.fn>;
  findByName: ReturnType<typeof vi.fn>;
} {
  return {
    loadMind: vi.fn().mockResolvedValue({ mindId: 'lucy-1234' }),
    setActiveMind: vi.fn(),
    findByName: overrides?.findByName ?? vi.fn(() => undefined),
  } as unknown as MindManager & {
    loadMind: ReturnType<typeof vi.fn>;
    setActiveMind: ReturnType<typeof vi.fn>;
    findByName: ReturnType<typeof vi.fn>;
  };
}

function createScaffold(): MindScaffold & {
  create: ReturnType<typeof vi.fn>;
  setProgressHandler: ReturnType<typeof vi.fn>;
} {
  return {
    create: vi.fn(),
    setProgressHandler: vi.fn(),
  } as unknown as MindScaffold & { create: ReturnType<typeof vi.fn>; setProgressHandler: ReturnType<typeof vi.fn> };
}

function createCatalog() {
  return {
    listTemplates: vi.fn().mockResolvedValue([lucyTemplate]),
  };
}

function createInstaller() {
  return {
    install: vi.fn().mockResolvedValue('C:\\agents\\lucy'),
  };
}
