import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { Repository } from '../../db/repository.js';
import { executeTool, setCalendarRepo, TOOL_DEFS } from './calendar.js';
import type { ToolContext } from './core.js';

function ctx(over: Partial<ToolContext & { ownerId: string | null }> = {}): ToolContext {
  return {
    workspacePath: '/tmp/dummy',
    editAllowed: true,
    spaceId: 'space-A',
    taskId: '7',
    ownerId: 'u1',
    ...over,
  } as ToolContext;
}

describe('calendar tools', () => {
  let tmpDir: string;
  let repo: Repository;

  beforeEach(() => {
    tmpDir = mkdtempSync(join(tmpdir(), 'calendar-tool-test-'));
    repo = new Repository(join(tmpDir, 'test.db'));
    setCalendarRepo(repo);
  });
  afterEach(() => {
    rmSync(tmpDir, { recursive: true, force: true });
    setCalendarRepo(null);
  });

  it('exposes TOOL_DEFS entries', () => {
    expect(TOOL_DEFS.AddCalendarEvent?.function.name).toBe('AddCalendarEvent');
    expect(TOOL_DEFS.ListCalendarEvents?.function.name).toBe('ListCalendarEvents');
  });

  it('AddCalendarEvent happy path: row created with createdBy=agent + sourceTaskId', async () => {
    const result = await executeTool(
      'AddCalendarEvent',
      { title: 'kickoff', date: '2026-07-01', time: '10:00', description: 'd' },
      ctx(),
    );
    expect(result?.isError).toBe(false);
    const events = await repo.listCalendarEvents('space-A');
    expect(events).toHaveLength(1);
    const ev = events[0]!;
    expect(ev.title).toBe('kickoff');
    expect(ev.time).toBe('10:00');
    expect(ev.createdBy).toBe('agent');
    expect(ev.sourceTaskId).toBe(7);
    expect(ev.ownerId).toBe('u1');
  });

  it('AddCalendarEvent all-day when time omitted', async () => {
    await executeTool('AddCalendarEvent', { title: 'deadline', date: '2026-07-10' }, ctx());
    const ev = (await repo.listCalendarEvents('space-A'))[0]!;
    expect(ev.time).toBeNull();
  });

  it('AddCalendarEvent: missing spaceId → isError, no row written', async () => {
    const result = await executeTool(
      'AddCalendarEvent',
      { title: 'x', date: '2026-07-01' },
      ctx({ spaceId: undefined }),
    );
    expect(result?.isError).toBe(true);
    expect(result?.output).toMatch(/スペース/);
  });

  it('AddCalendarEvent: bad date format → isError', async () => {
    const result = await executeTool('AddCalendarEvent', { title: 'x', date: '2026/07/01' }, ctx());
    expect(result?.isError).toBe(true);
    expect(result?.output).toMatch(/date/);
  });

  it('AddCalendarEvent: bad time format → isError', async () => {
    const result = await executeTool(
      'AddCalendarEvent',
      { title: 'x', date: '2026-07-01', time: '25:99' },
      ctx(),
    );
    expect(result?.isError).toBe(true);
    expect(result?.output).toMatch(/time/);
  });

  it('AddCalendarEvent: missing title → isError', async () => {
    const result = await executeTool('AddCalendarEvent', { date: '2026-07-01' }, ctx());
    expect(result?.isError).toBe(true);
    expect(result?.output).toMatch(/title/);
  });

  it('AddCalendarEvent: repo not injected → isError', async () => {
    setCalendarRepo(null);
    const result = await executeTool('AddCalendarEvent', { title: 'x', date: '2026-07-01' }, ctx());
    expect(result?.isError).toBe(true);
  });

  it('ListCalendarEvents returns the space events', async () => {
    await repo.createCalendarEvent({ spaceId: 'space-A', date: '2026-07-01', title: 'a' });
    await repo.createCalendarEvent({ spaceId: 'space-A', date: '2026-07-02', time: '09:00', title: 'b' });
    await repo.createCalendarEvent({ spaceId: 'space-B', date: '2026-07-01', title: 'other' });
    const result = await executeTool('ListCalendarEvents', {}, ctx());
    expect(result?.isError).toBe(false);
    expect(result?.output).toMatch(/a/);
    expect(result?.output).toMatch(/b/);
    expect(result?.output).not.toMatch(/other/);
  });

  it('ListCalendarEvents respects from/to range', async () => {
    await repo.createCalendarEvent({ spaceId: 'space-A', date: '2026-07-01', title: 'in' });
    await repo.createCalendarEvent({ spaceId: 'space-A', date: '2026-08-01', title: 'out' });
    const result = await executeTool('ListCalendarEvents', { from: '2026-07-01', to: '2026-07-31' }, ctx());
    expect(result?.output).toMatch(/in/);
    expect(result?.output).not.toMatch(/out/);
  });

  it('ListCalendarEvents: empty range message', async () => {
    const result = await executeTool('ListCalendarEvents', {}, ctx());
    expect(result?.isError).toBe(false);
    expect(result?.output).toMatch(/予定はありません/);
  });

  it('returns null for unrelated tool names', async () => {
    const result = await executeTool('SomethingElse', {}, ctx());
    expect(result).toBeNull();
  });
});
