import { afterEach, describe, it, expect, beforeEach } from 'vitest';
import { mkdtempSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { Repository } from './repository.js';
import { runMigrations } from './migrate.js';

describe('Repository workspace file provenance', () => {
  let tempDir = '';
  let repo: Repository;
  const WS_A = '/ws/a';
  const WS_B = '/ws/b';

  beforeEach(() => {
    tempDir = mkdtempSync(join(tmpdir(), 'maestro-prov-'));
    repo = new Repository(join(tempDir, 'orchestrator.db'));
    runMigrations(repo.getDb());
  });

  afterEach(() => {
    repo.close();
    if (tempDir) {
      rmSync(tempDir, { recursive: true, force: true });
      tempDir = '';
    }
  });

  it('records a new agent_output file with creator identity', () => {
    repo.recordProvenance({
      workspacePath: WS_A,
      relPath: 'output/report.md',
      event: 'create',
      sourceKind: 'agent_output',
      taskId: 7,
      jobId: 'job-1',
      piece: 'chat',
      movement: 'execute',
    });
    const rec = repo.getProvenance(WS_A, 'output/report.md');
    expect(rec).not.toBeNull();
    expect(rec!.sourceKind).toBe('agent_output');
    expect(rec!.createdByTaskId).toBe(7);
    expect(rec!.createdByPiece).toBe('chat');
    expect(rec!.lastModifiedByTaskId).toBe(7);
  });

  it('modify preserves original creator but updates last modifier', () => {
    repo.recordProvenance({ workspacePath: WS_A, relPath: 'output/a.md', event: 'create', sourceKind: 'agent_output', taskId: 1, jobId: 'j1' });
    repo.recordProvenance({ workspacePath: WS_A, relPath: 'output/a.md', event: 'modify', taskId: 2, jobId: 'j2' });
    const rec = repo.getProvenance(WS_A, 'output/a.md')!;
    expect(rec.createdByTaskId).toBe(1);
    expect(rec.sourceKind).toBe('agent_output');
    expect(rec.lastModifiedByTaskId).toBe(2);
    expect(rec.lastModifiedByJobId).toBe('j2');
  });

  it('a second create on an existing row preserves the original creator and source_kind', () => {
    repo.recordProvenance({ workspacePath: WS_A, relPath: 'output/c.md', event: 'create', sourceKind: 'user_input', taskId: 1 });
    // A later create event on the same path (e.g. re-upload / re-write) must not
    // reset the original creator or source_kind — only advance last-modified.
    repo.recordProvenance({ workspacePath: WS_A, relPath: 'output/c.md', event: 'create', sourceKind: 'agent_output', taskId: 2 });
    const rec = repo.getProvenance(WS_A, 'output/c.md')!;
    expect(rec.createdByTaskId).toBe(1);
    expect(rec.sourceKind).toBe('user_input');
    expect(rec.lastModifiedByTaskId).toBe(2);
  });

  it('listProvenance enforces a hard row cap regardless of a larger requested limit', () => {
    for (let i = 0; i < 550; i++) {
      repo.recordProvenance({ workspacePath: WS_A, relPath: `output/f${i}.md`, event: 'create', sourceKind: 'agent_output', taskId: 1 });
    }
    const rows = repo.listProvenance(WS_A, { limit: 100000 });
    expect(rows.length).toBeLessThanOrEqual(500);
    expect(rows.length).toBeGreaterThan(0);
  });

  it('modify of an untracked pre-existing file backfills as imported_existing', () => {
    repo.recordProvenance({ workspacePath: WS_A, relPath: 'output/old.md', event: 'modify', taskId: 5 });
    const rec = repo.getProvenance(WS_A, 'output/old.md')!;
    expect(rec.sourceKind).toBe('imported_existing');
    expect(rec.createdByTaskId).toBeNull();
    expect(rec.lastModifiedByTaskId).toBe(5);
  });

  it('records user_input uploads', () => {
    repo.recordProvenance({ workspacePath: WS_A, relPath: 'input/data.csv', event: 'create', sourceKind: 'user_input', taskId: 3 });
    expect(repo.getProvenance(WS_A, 'input/data.csv')!.sourceKind).toBe('user_input');
  });

  it('getProvenance returns null for unknown file', () => {
    expect(repo.getProvenance(WS_A, 'nope.txt')).toBeNull();
  });

  it('WORKSPACE SCOPING: a query for workspace A never returns workspace B rows', () => {
    repo.recordProvenance({ workspacePath: WS_A, relPath: 'output/x.md', event: 'create', sourceKind: 'agent_output', taskId: 1 });
    repo.recordProvenance({ workspacePath: WS_B, relPath: 'output/x.md', event: 'create', sourceKind: 'agent_output', taskId: 99 });
    // Same rel_path in both workspaces — the UNIQUE is (workspace_path, rel_path).
    expect(repo.getProvenance(WS_A, 'output/x.md')!.createdByTaskId).toBe(1);
    expect(repo.getProvenance(WS_B, 'output/x.md')!.createdByTaskId).toBe(99);
    const listA = repo.listProvenance(WS_A);
    expect(listA).toHaveLength(1);
    expect(listA[0]!.createdByTaskId).toBe(1);
  });

  it('listProvenance filters by sourceKind and createdByTaskId, bounded by limit', () => {
    repo.recordProvenance({ workspacePath: WS_A, relPath: 'output/a.md', event: 'create', sourceKind: 'agent_output', taskId: 1 });
    repo.recordProvenance({ workspacePath: WS_A, relPath: 'input/b.csv', event: 'create', sourceKind: 'user_input', taskId: 1 });
    repo.recordProvenance({ workspacePath: WS_A, relPath: 'output/c.md', event: 'create', sourceKind: 'agent_output', taskId: 2 });

    expect(repo.listProvenance(WS_A, { sourceKind: 'user_input' })).toHaveLength(1);
    expect(repo.listProvenance(WS_A, { createdByTaskId: 1 })).toHaveLength(2);
    expect(repo.listProvenance(WS_A, { pathPrefix: 'output/' })).toHaveLength(2);
    expect(repo.listProvenance(WS_A, { limit: 1 })).toHaveLength(1);
  });

  it('includeUnknown=false excludes unknown/imported_existing rows', () => {
    repo.recordProvenance({ workspacePath: WS_A, relPath: 'output/a.md', event: 'create', sourceKind: 'agent_output', taskId: 1 });
    repo.recordProvenance({ workspacePath: WS_A, relPath: 'output/old.md', event: 'modify', taskId: 2 }); // imported_existing
    expect(repo.listProvenance(WS_A, { includeUnknown: false })).toHaveLength(1);
    expect(repo.listProvenance(WS_A, { includeUnknown: true })).toHaveLength(2);
  });

  it('makeFileProvenanceIO binds reads to a single workspace path', () => {
    repo.recordProvenance({ workspacePath: WS_A, relPath: 'output/a.md', event: 'create', sourceKind: 'agent_output', taskId: 1 });
    repo.recordProvenance({ workspacePath: WS_B, relPath: 'output/a.md', event: 'create', sourceKind: 'agent_output', taskId: 2 });
    const io = repo.makeFileProvenanceIO(WS_A);
    expect(io.get('output/a.md')!.createdByTaskId).toBe(1);
    expect(io.list()).toHaveLength(1);
  });
});
