import { describe, it, expect } from 'vitest';
import { executeTool } from './file-provenance.js';
import type { ToolContext, FileProvenanceRecord, FileProvenanceIO } from './core.js';

function rec(partial: Partial<FileProvenanceRecord> & { relPath: string }): FileProvenanceRecord {
  return {
    relPath: partial.relPath,
    sourceKind: partial.sourceKind ?? 'agent_output',
    createdByTaskId: partial.createdByTaskId ?? null,
    createdByJobId: partial.createdByJobId ?? null,
    createdByPiece: partial.createdByPiece ?? null,
    createdByMovement: partial.createdByMovement ?? null,
    firstSeenAt: partial.firstSeenAt ?? null,
    lastModifiedByTaskId: partial.lastModifiedByTaskId ?? null,
    lastModifiedByJobId: partial.lastModifiedByJobId ?? null,
    lastModifiedAt: partial.lastModifiedAt ?? null,
    checksum: partial.checksum ?? null,
    note: partial.note ?? null,
  };
}

function ctxWith(records: FileProvenanceRecord[]): ToolContext {
  let lastFilters: Parameters<FileProvenanceIO['list']>[0];
  const io: FileProvenanceIO = {
    get: (relPath) => records.find((r) => r.relPath === relPath) ?? null,
    list: (filters) => {
      lastFilters = filters;
      return records;
    },
  };
  const ctx = { workspacePath: '/ws', editAllowed: true, fileProvenance: io } as ToolContext & {
    _lastFilters?: unknown;
  };
  Object.defineProperty(ctx, '_lastFilters', { get: () => lastFilters });
  return ctx;
}

describe('GetFileProvenance', () => {
  it('returns a compact record with task ids (never titles/user ids)', async () => {
    const ctx = ctxWith([rec({ relPath: 'output/a.md', createdByTaskId: 7, createdByPiece: 'chat', lastModifiedByTaskId: 9 })]);
    const res = await executeTool('GetFileProvenance', { path: 'output/a.md' }, ctx);
    expect(res!.isError).toBe(false);
    expect(res!.output).toContain('created_by_task_id: 7');
    expect(res!.output).toContain('last_modified_by_task_id: 9');
    expect(res!.output).not.toMatch(/user_id|title/i);
  });

  it('normalizes ./ and leading slash', async () => {
    const ctx = ctxWith([rec({ relPath: 'output/a.md', createdByTaskId: 1 })]);
    const res = await executeTool('GetFileProvenance', { path: './output/a.md' }, ctx);
    expect(res!.output).toContain('created_by_task_id: 1');
  });

  it('handles a missing row without crashing (backfill/unknown message)', async () => {
    const ctx = ctxWith([]);
    const res = await executeTool('GetFileProvenance', { path: 'output/missing.md' }, ctx);
    expect(res!.isError).toBe(false);
    expect(res!.output).toContain('unknown');
  });

  it('degrades to a no-op when provenance IO is unbound', async () => {
    const ctx = { workspacePath: '/ws', editAllowed: true } as ToolContext;
    const res = await executeTool('GetFileProvenance', { path: 'output/a.md' }, ctx);
    expect(res!.isError).toBe(false);
    expect(res!.output).toContain('利用できません');
  });
});

describe('ListWorkspaceFiles', () => {
  it('lists bounded provenance summaries without file contents', async () => {
    const ctx = ctxWith([
      rec({ relPath: 'output/a.md', sourceKind: 'agent_output', createdByTaskId: 1 }),
      rec({ relPath: 'input/b.csv', sourceKind: 'user_input', createdByTaskId: 1, lastModifiedByTaskId: 2 }),
    ]);
    const res = await executeTool('ListWorkspaceFiles', {}, ctx);
    expect(res!.output).toContain('output/a.md');
    expect(res!.output).toContain('[user_input]');
    expect(res!.output).toContain('last_modified_by=task 2');
  });

  it('passes filters through to the IO (sourceKind, createdByTaskId, includeUnknown, prefix)', async () => {
    const ctx = ctxWith([rec({ relPath: 'output/a.md', createdByTaskId: 1 })]) as ToolContext & { _lastFilters?: any };
    await executeTool('ListWorkspaceFiles', {
      path: 'output',
      sourceKind: 'agent_output',
      createdByTaskId: 1,
      includeUnknown: false,
    }, ctx);
    expect(ctx._lastFilters).toMatchObject({
      pathPrefix: 'output/',
      sourceKind: 'agent_output',
      createdByTaskId: 1,
      includeUnknown: false,
    });
  });

  it('caps limit at 200', async () => {
    const ctx = ctxWith([rec({ relPath: 'output/a.md' })]) as ToolContext & { _lastFilters?: any };
    await executeTool('ListWorkspaceFiles', { limit: 9999 }, ctx);
    expect(ctx._lastFilters.limit).toBe(200);
  });
});
