import { describe, it, expect } from 'vitest';
import Fastify, { type FastifyInstance } from 'fastify';
import { registerAgentSessionRoutes } from '../agent-sessions.js';
import type { Sql } from '../../db.js';

// Mock the porsager surface this route uses: a tagged-template `sql` dispatched by
// query substring. Two queries: the session-existence check and the agent_sessions
// JOIN. We return post-coercion shapes (booleans/strings) exactly as porsager would
// hand them to the route — `has_session` already a JS boolean, `last_active_at` a
// string|null — so the asserted JSON matches the API contract end-to-end.
interface MockState {
  sessionExists: boolean;
  rows: Array<{ agent: string; status: string; has_session: boolean; last_active_at: string | null }>;
}

function mockSql(state: MockState): Sql {
  return ((strings: TemplateStringsArray) => {
    const q = strings.join('');
    if (q.includes('SELECT id FROM sessions')) {
      return Promise.resolve(state.sessionExists ? [{ id: 'session-1' }] : []);
    }
    if (q.includes('FROM agent_sessions')) {
      return Promise.resolve(state.rows);
    }
    return Promise.resolve([]);
  }) as unknown as Sql;
}

function buildApp(state: MockState): FastifyInstance {
  const app = Fastify();
  registerAgentSessionRoutes(app, mockSql(state));
  return app;
}

describe('GET /api/sessions/:id/agent-sessions', () => {
  it('returns the per-(chat,agent) rows in the contracted shape', async () => {
    const app = buildApp({
      sessionExists: true,
      rows: [
        { agent: 'opencode', status: 'active', has_session: true, last_active_at: '2026-05-31T12:00:00.000Z' },
        { agent: 'goose', status: 'idle', has_session: false, last_active_at: null },
      ],
    });
    const res = await app.inject({ method: 'GET', url: '/api/sessions/session-1/agent-sessions' });
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(Array.isArray(body)).toBe(true);
    expect(body).toEqual([
      { agent: 'opencode', status: 'active', has_session: true, last_active_at: '2026-05-31T12:00:00.000Z' },
      { agent: 'goose', status: 'idle', has_session: false, last_active_at: null },
    ]);
    // Contract field types.
    expect(typeof body[0].agent).toBe('string');
    expect(typeof body[0].status).toBe('string');
    expect(typeof body[0].has_session).toBe('boolean');
    expect(body[1].last_active_at).toBeNull();
    await app.close();
  });

  it('returns an empty array when the session has no agent_sessions rows', async () => {
    const app = buildApp({ sessionExists: true, rows: [] });
    const res = await app.inject({ method: 'GET', url: '/api/sessions/session-1/agent-sessions' });
    expect(res.statusCode).toBe(200);
    expect(res.json()).toEqual([]);
    await app.close();
  });

  it('404s when the session does not exist', async () => {
    const app = buildApp({ sessionExists: false, rows: [] });
    const res = await app.inject({ method: 'GET', url: '/api/sessions/nope/agent-sessions' });
    expect(res.statusCode).toBe(404);
    expect(res.json()).toEqual({ error: 'session not found' });
    await app.close();
  });
});
