import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { Repository } from './repository';
import fs from 'fs';
import path from 'path';
import os from 'os';

describe('Repository a2a_tasks CRUD', () => {
  let repo: Repository;
  let dbPath: string;

  beforeEach(async () => {
    // Create a temporary database for testing
    const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'a2a-test-'));
    dbPath = path.join(tmpDir, 'test.db');
    repo = new Repository(dbPath);
  });

  afterEach(() => {
    // Clean up
    if (fs.existsSync(dbPath)) {
      fs.unlinkSync(dbPath);
    }
  });

  it('saveA2aTask and loadA2aTask round-trip', () => {
    const payload = {
      name: 'test-task',
      type: 'execution',
      config: { timeout: 5000 },
    };

    const row = {
      id: 'task-123',
      contextId: 'ctx-456',
      jobId: 'job-789',
      localTaskId: 42,
      payload,
    };

    // Save
    repo.saveA2aTask(row);

    // Load
    const loaded = repo.loadA2aTask('task-123');
    expect(loaded).toBeDefined();
    expect(loaded?.payload).toEqual(payload);
  });

  it('saveA2aTask upserts existing task', () => {
    const initialPayload = { version: 1, data: 'initial' };
    const updatedPayload = { version: 2, data: 'updated' };

    // First insert
    repo.saveA2aTask({
      id: 'task-upsert',
      contextId: 'ctx-1',
      jobId: 'job-1',
      localTaskId: 10,
      payload: initialPayload,
    });

    // Verify first insert
    let loaded = repo.loadA2aTask('task-upsert');
    expect(loaded?.payload).toEqual(initialPayload);

    // Upsert with new values
    repo.saveA2aTask({
      id: 'task-upsert',
      contextId: 'ctx-2',
      jobId: 'job-2',
      localTaskId: 20,
      payload: updatedPayload,
    });

    // Verify upserted
    loaded = repo.loadA2aTask('task-upsert');
    expect(loaded?.payload).toEqual(updatedPayload);
  });

  it('getA2aTaskByJobId reverse lookup', () => {
    const payload = { jobData: 'test-value' };

    repo.saveA2aTask({
      id: 'task-job-lookup',
      contextId: 'ctx-789',
      jobId: 'specific-job-id',
      localTaskId: 99,
      payload,
    });

    const found = repo.getA2aTaskByJobId('specific-job-id');
    expect(found).toBeDefined();
    expect(found?.id).toBe('task-job-lookup');
    expect(found?.payload).toEqual(payload);
  });

  it('getA2aTaskByJobId returns undefined for non-existent jobId', () => {
    const result = repo.getA2aTaskByJobId('non-existent-job');
    expect(result).toBeUndefined();
  });

  it('loadA2aTask returns undefined for non-existent id', () => {
    const result = repo.loadA2aTask('non-existent-task');
    expect(result).toBeUndefined();
  });

  it('handles null contextId, jobId, localTaskId', () => {
    const payload = { nullable: true };

    repo.saveA2aTask({
      id: 'task-nulls',
      contextId: null,
      jobId: null,
      localTaskId: null,
      payload,
    });

    const loaded = repo.loadA2aTask('task-nulls');
    expect(loaded?.payload).toEqual(payload);
  });

  // --- delegation columns (plan2c-1 task2) ---

  it('saveA2aTask persists delegationId, grantId, actingUserId', () => {
    repo.saveA2aTask({
      id: 'task-deleg',
      contextId: null,
      jobId: null,
      localTaskId: null,
      payload: { status: { state: 'submitted' } },
      delegationId: 'deleg-111',
      grantId: 'grant-222',
      actingUserId: 'user-333',
    });

    // Read back via raw query to verify columns were written
    const db = (repo as any).db;
    const row = db.prepare(
      'SELECT delegation_id, grant_id, acting_user_id FROM a2a_tasks WHERE id = ?'
    ).get('task-deleg') as { delegation_id: string; grant_id: string; acting_user_id: string };

    expect(row).toBeDefined();
    expect(row.delegation_id).toBe('deleg-111');
    expect(row.grant_id).toBe('grant-222');
    expect(row.acting_user_id).toBe('user-333');
  });

  it('saveA2aTask is backward-compatible: omitting delegation fields leaves them null', () => {
    repo.saveA2aTask({
      id: 'task-no-deleg',
      contextId: null,
      jobId: null,
      localTaskId: null,
      payload: { status: { state: 'working' } },
    });

    const db = (repo as any).db;
    const row = db.prepare(
      'SELECT delegation_id, grant_id, acting_user_id FROM a2a_tasks WHERE id = ?'
    ).get('task-no-deleg') as { delegation_id: string | null; grant_id: string | null; acting_user_id: string | null };

    expect(row.delegation_id).toBeNull();
    expect(row.grant_id).toBeNull();
    expect(row.acting_user_id).toBeNull();
  });

  it('listNonTerminalA2aTasks returns working tasks and excludes completed', () => {
    // working — non-terminal
    repo.saveA2aTask({
      id: 'task-working',
      contextId: null,
      jobId: 'job-w',
      localTaskId: null,
      payload: { id: 'task-working', kind: 'task', status: { state: 'working' } },
    });

    // completed — terminal, must be excluded
    repo.saveA2aTask({
      id: 'task-done',
      contextId: null,
      jobId: 'job-d',
      localTaskId: null,
      payload: { id: 'task-done', kind: 'task', status: { state: 'completed' } },
    });

    // failed — terminal, must be excluded
    repo.saveA2aTask({
      id: 'task-failed',
      contextId: null,
      jobId: 'job-f',
      localTaskId: null,
      payload: { id: 'task-failed', kind: 'task', status: { state: 'failed' } },
    });

    const results = repo.listNonTerminalA2aTasks();
    const ids = results.map(r => r.id);

    expect(ids).toContain('task-working');
    expect(ids).not.toContain('task-done');
    expect(ids).not.toContain('task-failed');
  });

  it('listNonTerminalA2aTasks treats tasks with no status.state as non-terminal (safe default)', () => {
    repo.saveA2aTask({
      id: 'task-no-state',
      contextId: null,
      jobId: null,
      localTaskId: null,
      payload: { id: 'task-no-state', kind: 'task' },
    });

    const results = repo.listNonTerminalA2aTasks();
    const ids = results.map(r => r.id);
    expect(ids).toContain('task-no-state');
  });

  it('listNonTerminalA2aTasks returns correct shape (id, jobId, payload)', () => {
    repo.saveA2aTask({
      id: 'task-shape',
      contextId: null,
      jobId: 'job-shape',
      localTaskId: null,
      payload: { id: 'task-shape', kind: 'task', status: { state: 'submitted' } },
    });

    const results = repo.listNonTerminalA2aTasks();
    const row = results.find(r => r.id === 'task-shape');
    expect(row).toBeDefined();
    expect(row!.jobId).toBe('job-shape');
    expect(row!.payload).toMatchObject({ status: { state: 'submitted' } });
  });

  it('listNonTerminalA2aTasks excludes canceled and rejected', () => {
    repo.saveA2aTask({
      id: 'task-canceled',
      contextId: null,
      jobId: null,
      localTaskId: null,
      payload: { id: 'task-canceled', kind: 'task', status: { state: 'canceled' } },
    });
    repo.saveA2aTask({
      id: 'task-rejected',
      contextId: null,
      jobId: null,
      localTaskId: null,
      payload: { id: 'task-rejected', kind: 'task', status: { state: 'rejected' } },
    });

    const results = repo.listNonTerminalA2aTasks();
    const ids = results.map(r => r.id);
    expect(ids).not.toContain('task-canceled');
    expect(ids).not.toContain('task-rejected');
  });
});
