/**
 * DrizzleTaskRepository Tests
 *
 * Tests for the Task repository adapter.
 */

import { describe, it, expect, beforeEach } from 'vitest';
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('DrizzleTaskRepository', () => {
  let db: DrizzleDatabase;
  let taskRepo: DrizzleTaskRepository;
  
  let phaseRepo: DrizzlePhaseRepository;
  let initiativeRepo: DrizzleInitiativeRepository;
  let testPhaseId: string;

  beforeEach(async () => {
    db = createTestDatabase();
    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',
    });
    testPhaseId = phase.id;
  });

  describe('create', () => {
    it('should create a task with generated id and timestamps', async () => {
      const task = await taskRepo.create({
        phaseId: testPhaseId,
        name: 'Test Task',
        description: 'A test task',
        order: 1,
      });

      expect(task.id).toBeDefined();
      expect(task.id.length).toBeGreaterThan(0);
      expect(task.phaseId).toBe(testPhaseId);
      expect(task.name).toBe('Test Task');
      expect(task.type).toBe('auto');
      expect(task.priority).toBe('medium');
      expect(task.status).toBe('pending');
      expect(task.order).toBe(1);
      expect(task.createdAt).toBeInstanceOf(Date);
      expect(task.updatedAt).toBeInstanceOf(Date);
    });

    it('should throw for invalid phaseId (FK constraint)', async () => {
      await expect(
        taskRepo.create({
          phaseId: 'invalid-phase-id',
          name: 'Invalid Task',
          order: 1,
        })
      ).rejects.toThrow();
    });

    it('should accept custom type and priority', async () => {
      const task = await taskRepo.create({
        phaseId: testPhaseId,
        name: 'High Priority Task',
        type: 'auto',
        priority: 'high',
        order: 1,
      });

      expect(task.type).toBe('auto');
      expect(task.priority).toBe('high');
    });
  });

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

    it('should find an existing task', async () => {
      const created = await taskRepo.create({
        phaseId: testPhaseId,
        name: 'Find Me',
        order: 1,
      });

      const found = await taskRepo.findById(created.id);
      expect(found).not.toBeNull();
      expect(found!.id).toBe(created.id);
      expect(found!.name).toBe('Find Me');
    });
  });

  describe('findByPhaseId', () => {
    it('should return empty array for phase with no tasks', async () => {
      const tasks = await taskRepo.findByPhaseId(testPhaseId);
      expect(tasks).toEqual([]);
    });

    it('should return only matching tasks ordered by order field', async () => {
      // Create tasks out of order
      await taskRepo.create({
        phaseId: testPhaseId,
        name: 'Task 3',
        order: 3,
      });
      await taskRepo.create({
        phaseId: testPhaseId,
        name: 'Task 1',
        order: 1,
      });
      await taskRepo.create({
        phaseId: testPhaseId,
        name: 'Task 2',
        order: 2,
      });

      const tasks = await taskRepo.findByPhaseId(testPhaseId);
      expect(tasks.length).toBe(3);
      expect(tasks[0].name).toBe('Task 1');
      expect(tasks[1].name).toBe('Task 2');
      expect(tasks[2].name).toBe('Task 3');
    });
  });

  describe('update', () => {
    it('should update status correctly', async () => {
      const created = await taskRepo.create({
        phaseId: testPhaseId,
        name: 'Status Test',
        status: 'pending',
        order: 1,
      });

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

      expect(updated.status).toBe('in_progress');
    });

    it('should update fields and updatedAt', async () => {
      const created = await taskRepo.create({
        phaseId: testPhaseId,
        name: 'Original Name',
        order: 1,
      });

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

      const updated = await taskRepo.update(created.id, {
        name: 'Updated Name',
        priority: 'low',
      });

      expect(updated.name).toBe('Updated Name');
      expect(updated.priority).toBe('low');
      expect(updated.updatedAt.getTime()).toBeGreaterThanOrEqual(created.updatedAt.getTime());
    });

    it('should throw for non-existent task', async () => {
      await expect(
        taskRepo.update('non-existent-id', { name: 'New Name' })
      ).rejects.toThrow('Task not found');
    });
  });

  describe('delete', () => {
    it('should delete an existing task', async () => {
      const created = await taskRepo.create({
        phaseId: testPhaseId,
        name: 'To Delete',
        order: 1,
      });

      await taskRepo.delete(created.id);

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

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