/**
 * DrizzleAgentRepository Tests
 *
 * Tests for the Agent repository adapter.
 */

import { describe, it, expect, beforeEach } from 'vitest';
import { DrizzleAgentRepository } from './agent.js';
import { DrizzleTaskRepository } from './task.js';
import { DrizzlePhaseRepository } from './phase.js';
import { DrizzleInitiativeRepository } from './initiative.js';
import { createTestDatabase } from './test-helpers.js';
import type { DrizzleDatabase } from '../../index.js';

describe('DrizzleAgentRepository', () => {
  let db: DrizzleDatabase;
  let agentRepo: DrizzleAgentRepository;
  let taskRepo: DrizzleTaskRepository;
  let phaseRepo: DrizzlePhaseRepository;
  let initiativeRepo: DrizzleInitiativeRepository;
  let testTaskId: string;

  beforeEach(async () => {
    db = createTestDatabase();
    agentRepo = new DrizzleAgentRepository(db);
    taskRepo = new DrizzleTaskRepository(db);
    phaseRepo = new DrizzlePhaseRepository(db);
    initiativeRepo = new DrizzleInitiativeRepository(db);

    // Create full hierarchy for FK constraint
    const initiative = await initiativeRepo.create({
      name: 'Test Initiative',
    });
    const phase = await phaseRepo.create({
      initiativeId: initiative.id,
      name: 'Test Phase',
    });
    const task = await taskRepo.create({
      phaseId: phase.id,
      name: 'Test Task',
      order: 1,
    });
    testTaskId = task.id;
  });

  describe('create', () => {
    it('should create an agent with generated id and timestamps', async () => {
      const agent = await agentRepo.create({
        name: 'gastown',
        worktreeId: 'worktree-123',
        taskId: testTaskId,
      });

      expect(agent.id).toBeDefined();
      expect(agent.id.length).toBeGreaterThan(0);
      expect(agent.name).toBe('gastown');
      expect(agent.worktreeId).toBe('worktree-123');
      expect(agent.taskId).toBe(testTaskId);
      expect(agent.sessionId).toBeNull();
      expect(agent.status).toBe('idle');
      expect(agent.createdAt).toBeInstanceOf(Date);
      expect(agent.updatedAt).toBeInstanceOf(Date);
    });

    it('should create agent without taskId', async () => {
      const agent = await agentRepo.create({
        name: 'standalone',
        worktreeId: 'worktree-456',
      });

      expect(agent.taskId).toBeNull();
    });

    it('should reject duplicate names', async () => {
      await agentRepo.create({
        name: 'unique-name',
        worktreeId: 'worktree-1',
      });

      await expect(
        agentRepo.create({
          name: 'unique-name',
          worktreeId: 'worktree-2',
        })
      ).rejects.toThrow();
    });
  });

  describe('findById', () => {
    it('should return null for non-existent agent', async () => {
      const result = await agentRepo.findById('non-existent-id');
      expect(result).toBeNull();
    });

    it('should find an existing agent', async () => {
      const created = await agentRepo.create({
        name: 'findme',
        worktreeId: 'worktree-123',
      });

      const found = await agentRepo.findById(created.id);
      expect(found).not.toBeNull();
      expect(found!.id).toBe(created.id);
      expect(found!.name).toBe('findme');
    });
  });

  describe('findByName', () => {
    it('should return null for non-existent name', async () => {
      const result = await agentRepo.findByName('nonexistent');
      expect(result).toBeNull();
    });

    it('should find agent by human-readable name', async () => {
      await agentRepo.create({
        name: 'chinatown',
        worktreeId: 'worktree-123',
      });

      const found = await agentRepo.findByName('chinatown');
      expect(found).not.toBeNull();
      expect(found!.name).toBe('chinatown');
    });
  });

  describe('findByTaskId', () => {
    it('should return null when no agent assigned to task', async () => {
      const result = await agentRepo.findByTaskId(testTaskId);
      expect(result).toBeNull();
    });

    it('should find agent by task', async () => {
      await agentRepo.create({
        name: 'task-agent',
        worktreeId: 'worktree-123',
        taskId: testTaskId,
      });

      const found = await agentRepo.findByTaskId(testTaskId);
      expect(found).not.toBeNull();
      expect(found!.taskId).toBe(testTaskId);
    });
  });

  describe('findBySessionId', () => {
    it('should return null when no agent has session', async () => {
      const result = await agentRepo.findBySessionId('session-123');
      expect(result).toBeNull();
    });

    it('should find agent by session ID', async () => {
      const agent = await agentRepo.create({
        name: 'session-agent',
        worktreeId: 'worktree-123',
      });
      await agentRepo.update(agent.id, { sessionId: 'session-abc' });

      const found = await agentRepo.findBySessionId('session-abc');
      expect(found).not.toBeNull();
      expect(found!.sessionId).toBe('session-abc');
    });
  });

  describe('findAll', () => {
    it('should return empty array when no agents', async () => {
      const agents = await agentRepo.findAll();
      expect(agents).toEqual([]);
    });

    it('should return all agents', async () => {
      await agentRepo.create({ name: 'agent-1', worktreeId: 'wt-1' });
      await agentRepo.create({ name: 'agent-2', worktreeId: 'wt-2' });
      await agentRepo.create({ name: 'agent-3', worktreeId: 'wt-3' });

      const agents = await agentRepo.findAll();
      expect(agents.length).toBe(3);
    });
  });

  describe('findByStatus', () => {
    it('should return empty array when no agents have status', async () => {
      const agents = await agentRepo.findByStatus('running');
      expect(agents).toEqual([]);
    });

    it('should filter by status correctly', async () => {
      const agent1 = await agentRepo.create({
        name: 'idle-agent',
        worktreeId: 'wt-1',
      });
      const agent2 = await agentRepo.create({
        name: 'running-agent',
        worktreeId: 'wt-2',
      });
      await agentRepo.update(agent2.id, { status: 'running' });

      const idleAgents = await agentRepo.findByStatus('idle');
      const runningAgents = await agentRepo.findByStatus('running');

      expect(idleAgents.length).toBe(1);
      expect(idleAgents[0].name).toBe('idle-agent');
      expect(runningAgents.length).toBe(1);
      expect(runningAgents[0].name).toBe('running-agent');
    });

    it('should filter by waiting_for_input status', async () => {
      const agent = await agentRepo.create({
        name: 'waiting-agent',
        worktreeId: 'wt-1',
      });
      await agentRepo.update(agent.id, { status: 'waiting_for_input' });

      const waitingAgents = await agentRepo.findByStatus('waiting_for_input');
      expect(waitingAgents.length).toBe(1);
      expect(waitingAgents[0].status).toBe('waiting_for_input');
    });
  });

  describe('update', () => {
    it('should change status and updatedAt', async () => {
      const created = await agentRepo.create({
        name: 'status-test',
        worktreeId: 'wt-1',
      });

      await new Promise((resolve) => setTimeout(resolve, 10));

      const updated = await agentRepo.update(created.id, { status: 'running' });

      expect(updated.status).toBe('running');
      expect(updated.updatedAt.getTime()).toBeGreaterThanOrEqual(
        created.updatedAt.getTime()
      );
    });

    it('should change sessionId and updatedAt', async () => {
      const created = await agentRepo.create({
        name: 'session-test',
        worktreeId: 'wt-1',
      });
      expect(created.sessionId).toBeNull();

      await new Promise((resolve) => setTimeout(resolve, 10));

      const updated = await agentRepo.update(created.id, { sessionId: 'new-session-id' });

      expect(updated.sessionId).toBe('new-session-id');
      expect(updated.updatedAt.getTime()).toBeGreaterThanOrEqual(
        created.updatedAt.getTime()
      );
    });

    it('should throw for non-existent agent', async () => {
      await expect(
        agentRepo.update('non-existent-id', { status: 'running' })
      ).rejects.toThrow('Agent not found');
    });
  });

  describe('delete', () => {
    it('should delete an existing agent', async () => {
      const created = await agentRepo.create({
        name: 'to-delete',
        worktreeId: 'wt-1',
      });

      await agentRepo.delete(created.id);

      const found = await agentRepo.findById(created.id);
      expect(found).toBeNull();
    });

    it('should throw for non-existent agent', async () => {
      await expect(agentRepo.delete('non-existent-id')).rejects.toThrow(
        'Agent not found'
      );
    });
  });
});
