/**
 * Integration tests for initiative tRPC router — qualityReview field and listInitiatives sort.
 *
 * Verifies that updateInitiativeConfig accepts and persists qualityReview,
 * and that listInitiatives returns initiatives in priority-sorted order.
 */

import { describe, it, expect, vi } from 'vitest';
import { eq } from 'drizzle-orm';
import { router, publicProcedure, createCallerFactory } from '../trpc.js';
import { initiativeProcedures } from './initiative.js';
import type { TRPCContext } from '../context.js';
import { createTestDatabase } from '../../db/repositories/drizzle/test-helpers.js';
import { DrizzleInitiativeRepository, DrizzlePhaseRepository } from '../../db/repositories/drizzle/index.js';
import { initiatives as initiativesTable } from '../../db/schema.js';

// =============================================================================
// Mock ensureProjectClone — prevents actual git cloning
// =============================================================================

vi.mock('../../git/project-clones.js', () => ({
  ensureProjectClone: vi.fn().mockResolvedValue('/fake/clone/path'),
  getProjectCloneDir: vi.fn().mockReturnValue('repos/fake-project-id'),
}));

// =============================================================================
// Test router
// =============================================================================

const testRouter = router({
  ...initiativeProcedures(publicProcedure),
});

const createCaller = createCallerFactory(testRouter);

// =============================================================================
// Setup helper
// =============================================================================

function createMockEventBus(): TRPCContext['eventBus'] {
  return {
    emit: vi.fn(),
    on: vi.fn(),
    off: vi.fn(),
    once: vi.fn(),
  };
}

async function setup() {
  const db = createTestDatabase();
  const initiativeRepo = new DrizzleInitiativeRepository(db);
  const ctx: TRPCContext = {
    eventBus: createMockEventBus(),
    serverStartedAt: null,
    processCount: 0,
    initiativeRepository: initiativeRepo,
  };
  const caller = createCaller(ctx);
  const initiative = await initiativeRepo.create({ name: 'Test Initiative' });
  return { caller, initiativeRepo, initiative };
}

// =============================================================================
// Tests
// =============================================================================

// =============================================================================
// listInitiatives sort order tests
// =============================================================================

describe('listInitiatives — sort order', () => {
  async function setupWithPhaseRepo() {
    const db = createTestDatabase();
    const initiativeRepo = new DrizzleInitiativeRepository(db);
    const phaseRepo = new DrizzlePhaseRepository(db);
    const ctx: TRPCContext = {
      eventBus: createMockEventBus(),
      serverStartedAt: null,
      processCount: 0,
      initiativeRepository: initiativeRepo,
      phaseRepository: phaseRepo,
    };
    const caller = createCaller(ctx);
    return { caller, initiativeRepo, phaseRepo, db };
  }

  async function setupNoPhaseRepo() {
    const db = createTestDatabase();
    const initiativeRepo = new DrizzleInitiativeRepository(db);
    const ctx: TRPCContext = {
      eventBus: createMockEventBus(),
      serverStartedAt: null,
      processCount: 0,
      initiativeRepository: initiativeRepo,
    };
    const caller = createCaller(ctx);
    return { caller, initiativeRepo, db };
  }

  it('executing appears before idle/planning (state priority 0 < 1)', async () => {
    const { caller, initiativeRepo, phaseRepo } = await setupWithPhaseRepo();
    const executingInit = await initiativeRepo.create({ name: 'Executing' });
    const planningInit = await initiativeRepo.create({ name: 'Planning' });
    await phaseRepo.create({ initiativeId: executingInit.id, name: 'Phase 1', status: 'in_progress' });
    // planningInit has no phases → derives 'idle' (priority 1 via fallback)
    // executingInit has in_progress phase → derives 'executing' (priority 0)
    const result = await caller.listInitiatives({});
    expect(result[0].id).toBe(executingInit.id);
  });

  it('ready appears before blocked (priority 1 < 2)', async () => {
    const { caller, initiativeRepo, phaseRepo } = await setupWithPhaseRepo();
    const blockedInit = await initiativeRepo.create({ name: 'Blocked' });
    const readyInit = await initiativeRepo.create({ name: 'Ready' });
    await phaseRepo.create({ initiativeId: blockedInit.id, name: 'Blocked Phase', status: 'blocked' });
    await phaseRepo.create({ initiativeId: readyInit.id, name: 'Ready Phase', status: 'approved' });
    const result = await caller.listInitiatives({});
    expect(result[0].id).toBe(readyInit.id);
  });

  it('same state — most recently updated comes first', async () => {
    const { caller, initiativeRepo, db } = await setupNoPhaseRepo();
    const older = await initiativeRepo.create({ name: 'Older' });
    const newer = await initiativeRepo.create({ name: 'Newer' });
    // Set different updatedAt values via raw DB (SQLite stores timestamps at second precision)
    const olderDate = new Date('2024-01-01T00:00:00.000Z');
    const newerDate = new Date('2024-01-02T00:00:00.000Z');
    await db.update(initiativesTable).set({ updatedAt: olderDate }).where(eq(initiativesTable.id, older.id));
    await db.update(initiativesTable).set({ updatedAt: newerDate }).where(eq(initiativesTable.id, newer.id));
    const result = await caller.listInitiatives({});
    expect(result[0].id).toBe(newer.id);
  });

  it('same state and updatedAt — lexicographically smaller id appears first', async () => {
    const { caller, initiativeRepo, db } = await setupNoPhaseRepo();
    const init1 = await initiativeRepo.create({ name: 'Init A' });
    const init2 = await initiativeRepo.create({ name: 'Init B' });
    const fixedDate = new Date('2024-01-01T00:00:00.000Z');
    await db.update(initiativesTable).set({ updatedAt: fixedDate }).where(eq(initiativesTable.id, init1.id));
    await db.update(initiativesTable).set({ updatedAt: fixedDate }).where(eq(initiativesTable.id, init2.id));
    const result = await caller.listInitiatives({});
    const expectedFirst = init1.id < init2.id ? init1.id : init2.id;
    expect(result[0].id).toBe(expectedFirst);
  });

  it('empty list returns []', async () => {
    const { caller } = await setupNoPhaseRepo();
    const result = await caller.listInitiatives({});
    expect(result).toEqual([]);
  });

  it('single item returns array of length 1', async () => {
    const { caller, initiativeRepo } = await setupNoPhaseRepo();
    await initiativeRepo.create({ name: 'Only' });
    const result = await caller.listInitiatives({});
    expect(result).toHaveLength(1);
  });
});

describe('updateInitiativeConfig — qualityReview', () => {
  it('sets qualityReview to true', async () => {
    const { caller, initiative } = await setup();
    const result = await caller.updateInitiativeConfig({
      initiativeId: initiative.id,
      qualityReview: true,
    });
    expect(result.qualityReview).toBe(true);
  });

  it('sets qualityReview to false', async () => {
    const { caller, initiative, initiativeRepo } = await setup();
    // First set it to true
    await initiativeRepo.update(initiative.id, { qualityReview: true });
    // Now flip it back
    const result = await caller.updateInitiativeConfig({
      initiativeId: initiative.id,
      qualityReview: false,
    });
    expect(result.qualityReview).toBe(false);
  });

  it('does not change qualityReview when omitted', async () => {
    const { caller, initiative, initiativeRepo } = await setup();
    // Set to true first
    await initiativeRepo.update(initiative.id, { qualityReview: true });
    // Update without qualityReview
    const result = await caller.updateInitiativeConfig({
      initiativeId: initiative.id,
      executionMode: 'yolo',
    });
    expect(result.qualityReview).toBe(true); // unchanged
  });
});
