/**
 * Cascade Delete Tests
 *
 * Tests that cascade deletes work correctly through the repository layer.
 * Verifies the SQLite foreign key cascade behavior configured in schema.ts.
 */

import { describe, it, expect, beforeEach } from 'vitest';
import { nanoid } from 'nanoid';
import { DrizzleInitiativeRepository } from './initiative.js';
import { DrizzlePhaseRepository } from './phase.js';
import { DrizzleTaskRepository } from './task.js';
import { DrizzlePageRepository } from './page.js';
import { DrizzleProjectRepository } from './project.js';
import { DrizzleChangeSetRepository } from './change-set.js';
import { DrizzleAgentRepository } from './agent.js';
import { DrizzleConversationRepository } from './conversation.js';
import { createTestDatabase } from './test-helpers.js';
import { changeSets } from '../../schema.js';
import type { DrizzleDatabase } from '../../index.js';

describe('Cascade Deletes', () => {
  let db: DrizzleDatabase;
  let initiativeRepo: DrizzleInitiativeRepository;
  let phaseRepo: DrizzlePhaseRepository;
  let taskRepo: DrizzleTaskRepository;
  let pageRepo: DrizzlePageRepository;
  let projectRepo: DrizzleProjectRepository;
  let changeSetRepo: DrizzleChangeSetRepository;
  let agentRepo: DrizzleAgentRepository;
  let conversationRepo: DrizzleConversationRepository;

  beforeEach(() => {
    db = createTestDatabase();
    initiativeRepo = new DrizzleInitiativeRepository(db);
    phaseRepo = new DrizzlePhaseRepository(db);
    taskRepo = new DrizzleTaskRepository(db);
    pageRepo = new DrizzlePageRepository(db);
    projectRepo = new DrizzleProjectRepository(db);
    changeSetRepo = new DrizzleChangeSetRepository(db);
    agentRepo = new DrizzleAgentRepository(db);
    conversationRepo = new DrizzleConversationRepository(db);
  });

  /**
   * Helper to create a full hierarchy for testing.
   * Uses parent tasks (detail category) to group child tasks.
   */
  async function createFullHierarchy() {
    const initiative = await initiativeRepo.create({
      name: 'Test Initiative',
    });

    const phase1 = await phaseRepo.create({
      initiativeId: initiative.id,
      name: 'Phase 1',
    });

    const phase2 = await phaseRepo.create({
      initiativeId: initiative.id,
      name: 'Phase 2',
    });

    // Create parent (detail) tasks that group child tasks
    const parentTask1 = await taskRepo.create({
      phaseId: phase1.id,
      initiativeId: initiative.id,
      name: 'Parent Task 1-1',
      category: 'detail',
      order: 1,
    });

    const parentTask2 = await taskRepo.create({
      phaseId: phase1.id,
      initiativeId: initiative.id,
      name: 'Parent Task 1-2',
      category: 'detail',
      order: 2,
    });

    const parentTask3 = await taskRepo.create({
      phaseId: phase2.id,
      initiativeId: initiative.id,
      name: 'Parent Task 2-1',
      category: 'detail',
      order: 1,
    });

    // Create child tasks under parent tasks
    const task1 = await taskRepo.create({
      parentTaskId: parentTask1.id,
      phaseId: phase1.id,
      initiativeId: initiative.id,
      name: 'Task 1-1-1',
      order: 1,
    });

    const task2 = await taskRepo.create({
      parentTaskId: parentTask1.id,
      phaseId: phase1.id,
      initiativeId: initiative.id,
      name: 'Task 1-1-2',
      order: 2,
    });

    const task3 = await taskRepo.create({
      parentTaskId: parentTask2.id,
      phaseId: phase1.id,
      initiativeId: initiative.id,
      name: 'Task 1-2-1',
      order: 1,
    });

    const task4 = await taskRepo.create({
      parentTaskId: parentTask3.id,
      phaseId: phase2.id,
      initiativeId: initiative.id,
      name: 'Task 2-1-1',
      order: 1,
    });

    // Create a page for the initiative
    const page = await pageRepo.create({
      initiativeId: initiative.id,
      parentPageId: null,
      title: 'Root Page',
      content: null,
      sortOrder: 0,
    });

    // Create a project and link it via junction table
    const project = await projectRepo.create({
      name: 'test-project',
      url: 'https://github.com/test/test-project.git',
    });
    await projectRepo.setInitiativeProjects(initiative.id, [project.id]);

    // Create two agents (need two for conversations, and one for changeSet FK)
    const agent1 = await agentRepo.create({
      name: 'agent-1',
      worktreeId: 'wt-1',
      initiativeId: initiative.id,
    });
    const agent2 = await agentRepo.create({
      name: 'agent-2',
      worktreeId: 'wt-2',
      initiativeId: initiative.id,
    });

    // Insert change set directly (createWithEntries uses async tx, incompatible with better-sqlite3 sync driver)
    const changeSetId = nanoid();
    await db.insert(changeSets).values({
      id: changeSetId,
      agentId: agent1.id,
      agentName: agent1.name,
      initiativeId: initiative.id,
      mode: 'plan',
      status: 'applied',
      createdAt: new Date(),
    });
    const changeSet = (await changeSetRepo.findById(changeSetId))!;

    // Create a conversation between agents with initiative context
    const conversation = await conversationRepo.create({
      fromAgentId: agent1.id,
      toAgentId: agent2.id,
      initiativeId: initiative.id,
      question: 'Test question',
    });

    return {
      initiative,
      phases: { phase1, phase2 },
      parentTasks: { parentTask1, parentTask2, parentTask3 },
      tasks: { task1, task2, task3, task4 },
      page,
      project,
      changeSet,
      agents: { agent1, agent2 },
      conversation,
    };
  }

  describe('delete initiative', () => {
    it('should cascade delete all phases, tasks, pages, junction rows, and change sets', async () => {
      const { initiative, phases, parentTasks, tasks, page, project, changeSet } =
        await createFullHierarchy();

      // Verify everything exists
      expect(await initiativeRepo.findById(initiative.id)).not.toBeNull();
      expect(await phaseRepo.findById(phases.phase1.id)).not.toBeNull();
      expect(await phaseRepo.findById(phases.phase2.id)).not.toBeNull();
      expect(await taskRepo.findById(parentTasks.parentTask1.id)).not.toBeNull();
      expect(await taskRepo.findById(parentTasks.parentTask2.id)).not.toBeNull();
      expect(await taskRepo.findById(parentTasks.parentTask3.id)).not.toBeNull();
      expect(await taskRepo.findById(tasks.task1.id)).not.toBeNull();
      expect(await taskRepo.findById(tasks.task2.id)).not.toBeNull();
      expect(await taskRepo.findById(tasks.task3.id)).not.toBeNull();
      expect(await taskRepo.findById(tasks.task4.id)).not.toBeNull();
      expect(await pageRepo.findById(page.id)).not.toBeNull();
      expect(await changeSetRepo.findById(changeSet.id)).not.toBeNull();
      const linkedProjects = await projectRepo.findProjectsByInitiativeId(initiative.id);
      expect(linkedProjects).toHaveLength(1);

      // Delete initiative
      await initiativeRepo.delete(initiative.id);

      // Verify cascade deletes — all gone
      expect(await initiativeRepo.findById(initiative.id)).toBeNull();
      expect(await phaseRepo.findById(phases.phase1.id)).toBeNull();
      expect(await phaseRepo.findById(phases.phase2.id)).toBeNull();
      expect(await taskRepo.findById(parentTasks.parentTask1.id)).toBeNull();
      expect(await taskRepo.findById(parentTasks.parentTask2.id)).toBeNull();
      expect(await taskRepo.findById(parentTasks.parentTask3.id)).toBeNull();
      expect(await taskRepo.findById(tasks.task1.id)).toBeNull();
      expect(await taskRepo.findById(tasks.task2.id)).toBeNull();
      expect(await taskRepo.findById(tasks.task3.id)).toBeNull();
      expect(await taskRepo.findById(tasks.task4.id)).toBeNull();
      expect(await pageRepo.findById(page.id)).toBeNull();
      expect(await changeSetRepo.findById(changeSet.id)).toBeNull();

      // Junction row gone but project itself survives
      const remainingLinks = await projectRepo.findProjectsByInitiativeId(initiative.id);
      expect(remainingLinks).toHaveLength(0);
      expect(await projectRepo.findById(project.id)).not.toBeNull();
    });

    it('should set null on agents and conversations (not cascade)', async () => {
      const { initiative, agents, conversation } = await createFullHierarchy();

      // Verify agents are linked
      const a1Before = await agentRepo.findById(agents.agent1.id);
      expect(a1Before!.initiativeId).toBe(initiative.id);

      // Delete initiative
      await initiativeRepo.delete(initiative.id);

      // Agents survive with initiativeId set to null
      const a1After = await agentRepo.findById(agents.agent1.id);
      expect(a1After).not.toBeNull();
      expect(a1After!.initiativeId).toBeNull();

      const a2After = await agentRepo.findById(agents.agent2.id);
      expect(a2After).not.toBeNull();
      expect(a2After!.initiativeId).toBeNull();

      // Conversation survives with initiativeId set to null
      const convAfter = await conversationRepo.findById(conversation.id);
      expect(convAfter).not.toBeNull();
      expect(convAfter!.initiativeId).toBeNull();
    });
  });

  describe('delete phase', () => {
    it('should cascade delete tasks under that phase only', async () => {
      const { initiative, phases, parentTasks, tasks } = await createFullHierarchy();

      // Delete phase 1
      await phaseRepo.delete(phases.phase1.id);

      // Initiative still exists
      expect(await initiativeRepo.findById(initiative.id)).not.toBeNull();

      // Phase 1 and its tasks are gone
      expect(await phaseRepo.findById(phases.phase1.id)).toBeNull();
      expect(await taskRepo.findById(parentTasks.parentTask1.id)).toBeNull();
      expect(await taskRepo.findById(parentTasks.parentTask2.id)).toBeNull();
      expect(await taskRepo.findById(tasks.task1.id)).toBeNull();
      expect(await taskRepo.findById(tasks.task2.id)).toBeNull();
      expect(await taskRepo.findById(tasks.task3.id)).toBeNull();

      // Phase 2 and its tasks still exist
      expect(await phaseRepo.findById(phases.phase2.id)).not.toBeNull();
      expect(await taskRepo.findById(parentTasks.parentTask3.id)).not.toBeNull();
      expect(await taskRepo.findById(tasks.task4.id)).not.toBeNull();
    });
  });

  describe('delete parent task', () => {
    it('should cascade delete child tasks under that parent only', async () => {
      const { phases, parentTasks, tasks } = await createFullHierarchy();

      // Delete parent task 1
      await taskRepo.delete(parentTasks.parentTask1.id);

      // Phase still exists
      expect(await phaseRepo.findById(phases.phase1.id)).not.toBeNull();

      // Parent task 1 and its children are gone
      expect(await taskRepo.findById(parentTasks.parentTask1.id)).toBeNull();
      expect(await taskRepo.findById(tasks.task1.id)).toBeNull();
      expect(await taskRepo.findById(tasks.task2.id)).toBeNull();

      // Other parent tasks and their children still exist
      expect(await taskRepo.findById(parentTasks.parentTask2.id)).not.toBeNull();
      expect(await taskRepo.findById(parentTasks.parentTask3.id)).not.toBeNull();
      expect(await taskRepo.findById(tasks.task3.id)).not.toBeNull();
      expect(await taskRepo.findById(tasks.task4.id)).not.toBeNull();
    });
  });
});
