/*
 * Integration tests for the REST + MCP surface.
 *
 * Spins up the real Express app on an ephemeral port (app is exported from
 * src/index.ts; its listen() is guarded by require.main so importing here does
 * NOT bind the production port). Storage uses the vitest-isolated data dir, so
 * these never touch the user's live data.
 */

import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import type { AddressInfo } from 'net';
import type { Server } from 'http';
import { app } from '../src/index';

let server: Server;
let BASE_URL: string;

beforeAll(async () => {
  await new Promise<void>((resolve) => {
    server = app.listen(0, () => {
      BASE_URL = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
      resolve();
    });
  });
});

afterAll(async () => {
  await new Promise<void>((resolve) => server.close(() => resolve()));
});

// /mcp (streamable) replies with an SSE frame; /mcp/legacy replies with plain
// JSON. Normalize both to the JSON-RPC object.
function parseRpc(contentType: string, text: string): Record<string, unknown> | null {
  if (contentType.includes('text/event-stream')) {
    const data = text.split('\n').find((l) => l.startsWith('data:'));
    return data ? JSON.parse(data.slice(5).trim()) : null;
  }
  return JSON.parse(text);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function rpc(path: string, body: object, acceptSse = false): Promise<any> {
  const res = await fetch(`${BASE_URL}${path}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      // Streamable /mcp negotiates SSE; legacy /mcp/legacy is plain JSON.
      Accept: acceptSse ? 'application/json, text/event-stream' : 'application/json',
    },
    body: JSON.stringify(body),
  });
  return parseRpc(res.headers.get('content-type') || '', await res.text());
}

describe('Progress Tracker API', () => {
  describe('Health Check', () => {
    it('returns ok status', async () => {
      const data = await (await fetch(`${BASE_URL}/health`)).json();
      expect(data.status).toBe('ok');
    });
  });

  describe('Projects API', () => {
    it('GET /projects returns an array', async () => {
      const data = await (await fetch(`${BASE_URL}/projects`)).json();
      expect(Array.isArray(data)).toBe(true);
    });
  });

  describe('Tasks API', () => {
    it('GET /tasks returns an array with P0 ahead of P1', async () => {
      const data = await (await fetch(`${BASE_URL}/tasks`)).json();
      expect(Array.isArray(data)).toBe(true);
      const priorities = data.map((t: { priority: string }) => t.priority);
      const p0 = priorities.indexOf('P0');
      const p1 = priorities.indexOf('P1');
      if (p0 !== -1 && p1 !== -1) expect(p0).toBeLessThan(p1);
    });

    it('GET /tasks/priority/P0 filters to P0', async () => {
      const data = await (await fetch(`${BASE_URL}/tasks/priority/P0`)).json();
      expect(Array.isArray(data)).toBe(true);
      data.forEach((t: { priority: string }) => expect(t.priority).toBe('P0'));
    });
  });

  describe('MCP Endpoint', () => {
    it('streamable /mcp responds to initialize with serverInfo', async () => {
      const json = await rpc('/mcp', {
        jsonrpc: '2.0',
        id: 1,
        method: 'initialize',
        params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'test', version: '0' } },
      }, true);
      expect(json.jsonrpc).toBe('2.0');
      expect(json.result?.serverInfo?.name).toBe('priority-forge');
    });

    it('legacy /mcp/legacy lists tools', async () => {
      const json = await rpc('/mcp/legacy', { jsonrpc: '2.0', id: 2, method: 'tools/list' });
      expect(Array.isArray(json.result?.tools)).toBe(true);
      const names = json.result.tools.map((t: { name: string }) => t.name);
      expect(names).toContain('get_status');
      expect(names).toContain('create_task');
      expect(names).toContain('complete_task');
    });

    it('legacy /mcp/legacy executes get_status', async () => {
      const json = await rpc('/mcp/legacy', {
        jsonrpc: '2.0',
        id: 3,
        method: 'tools/call',
        params: { name: 'get_status', arguments: {} },
      });
      expect(json.result?.content?.[0]?.type).toBe('text');
      // get_status text is the status JSON followed by a "---\n📋 Remember…"
      // reminder suffix; parse just the JSON prefix.
      const status = JSON.parse(json.result.content[0].text.split('\n\n---')[0]);
      expect(status.projects).toBeDefined();
      expect(status.priorityQueue).toBeDefined();
    });
  });
});
