import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtemp, rm, readFile, writeFile, mkdir } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

import { installManagedSessionHook, removeManagedSessionHook, MARKER_KEY } from '../../src/utils/sessionhook.js';

describe('sessionhook', () => {
  let tmp: string;

  beforeEach(async () => {
    tmp = await mkdtemp(join(tmpdir(), 'mcpctl-sessionhook-'));
  });

  afterEach(async () => {
    await rm(tmp, { recursive: true, force: true });
  });

  it('creates settings.json from scratch when missing', async () => {
    const path = join(tmp, 'settings.json');
    const result = await installManagedSessionHook('mcpctl skills sync --quiet', path);
    expect(result.updated).toBe(true);
    const settings = JSON.parse(await readFile(path, 'utf-8'));
    expect(settings.hooks.SessionStart).toHaveLength(1);
    const entry = settings.hooks.SessionStart[0].hooks[0];
    expect(entry.command).toBe('mcpctl skills sync --quiet');
    expect(entry[MARKER_KEY]).toBe(true);
  });

  it('is idempotent — re-running does not add duplicates', async () => {
    const path = join(tmp, 'settings.json');
    await installManagedSessionHook('mcpctl skills sync --quiet', path);
    const second = await installManagedSessionHook('mcpctl skills sync --quiet', path);
    expect(second.updated).toBe(false);
    const settings = JSON.parse(await readFile(path, 'utf-8'));
    const entries = settings.hooks.SessionStart.flatMap((g: { hooks: unknown[] }) => g.hooks);
    const managed = entries.filter((e: Record<string, unknown>) => e[MARKER_KEY] === true);
    expect(managed).toHaveLength(1);
  });

  it('updates the command in place when it changes', async () => {
    const path = join(tmp, 'settings.json');
    await installManagedSessionHook('mcpctl skills sync', path);
    const updated = await installManagedSessionHook('mcpctl skills sync --quiet', path);
    expect(updated.updated).toBe(true);
    const settings = JSON.parse(await readFile(path, 'utf-8'));
    const managed = settings.hooks.SessionStart
      .flatMap((g: { hooks: unknown[] }) => g.hooks)
      .find((e: Record<string, unknown>) => e[MARKER_KEY] === true);
    expect(managed.command).toBe('mcpctl skills sync --quiet');
  });

  it('preserves non-managed hooks', async () => {
    const path = join(tmp, 'settings.json');
    await mkdir(tmp, { recursive: true });
    await writeFile(path, JSON.stringify({
      hooks: {
        SessionStart: [{ hooks: [{ type: 'command', command: 'echo user-hook' }] }],
      },
    }));
    await installManagedSessionHook('mcpctl skills sync --quiet', path);
    const settings = JSON.parse(await readFile(path, 'utf-8'));
    const all = settings.hooks.SessionStart.flatMap((g: { hooks: unknown[] }) => g.hooks);
    expect(all).toHaveLength(2);
    expect(all.find((e: Record<string, unknown>) => e.command === 'echo user-hook')).toBeDefined();
    expect(all.find((e: Record<string, unknown>) => e[MARKER_KEY] === true)).toBeDefined();
  });

  it('remove drops the managed entry but keeps user hooks', async () => {
    const path = join(tmp, 'settings.json');
    await writeFile(path, JSON.stringify({
      hooks: {
        SessionStart: [{ hooks: [{ type: 'command', command: 'echo user' }] }],
      },
    }));
    await installManagedSessionHook('mcpctl skills sync --quiet', path);
    const removed = await removeManagedSessionHook(path);
    expect(removed.removed).toBe(true);
    const settings = JSON.parse(await readFile(path, 'utf-8'));
    const all = settings.hooks.SessionStart.flatMap((g: { hooks: unknown[] }) => g.hooks);
    expect(all).toHaveLength(1);
    expect(all[0].command).toBe('echo user');
  });

  it('remove is a no-op when no managed entry exists', async () => {
    const path = join(tmp, 'settings.json');
    const result = await removeManagedSessionHook(path);
    expect(result.removed).toBe(false);
  });

  it('survives empty settings.json', async () => {
    const path = join(tmp, 'settings.json');
    await writeFile(path, '');
    await installManagedSessionHook('mcpctl skills sync --quiet', path);
    const settings = JSON.parse(await readFile(path, 'utf-8'));
    expect(settings.hooks.SessionStart).toHaveLength(1);
  });

  it('strips line comments before parsing', async () => {
    const path = join(tmp, 'settings.json');
    await writeFile(path, '// a leading comment\n{\n  "hooks": {}\n}\n');
    await installManagedSessionHook('mcpctl skills sync --quiet', path);
    const settings = JSON.parse(await readFile(path, 'utf-8'));
    expect(settings.hooks.SessionStart).toHaveLength(1);
  });
});
