// Plan §3.F2 / spec §11.7 — daemon lifecycle endpoints.
//
// `od daemon status` and `od daemon stop` (both Phase 1.5) talk to the
// new /api/daemon/status + /api/daemon/shutdown routes. This suite spins
// the real Express app via `startServer` and asserts the public shape so
// the CLI can rely on it without re-checking each release.

import type http from 'node:http';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { startServer } from '../src/server.js';

let server: http.Server;
let baseUrl: string;
let shutdown: (() => Promise<void> | void) | undefined;

beforeAll(async () => {
  const started = (await startServer({ port: 0, returnServer: true })) as {
    url: string;
    server: http.Server;
    shutdown?: () => Promise<void> | void;
  };
  baseUrl = started.url;
  server = started.server;
  shutdown = started.shutdown;
});

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

describe('GET /api/daemon/status', () => {
  it('returns a runtime snapshot covering version + bindHost + port + pid + installedPlugins', async () => {
    const resp = await fetch(`${baseUrl}/api/daemon/status`);
    expect(resp.status).toBe(200);
    const body = (await resp.json()) as {
      ok: boolean;
      version: unknown;
      bindHost: unknown;
      port: unknown;
      sandboxMode: boolean;
      sandbox: { enabled: boolean };
      pid: unknown;
      installedPlugins: unknown;
      shuttingDown: boolean;
      namespace?: unknown;
    };
    expect(body.ok).toBe(true);
    expect(typeof body.version === 'string' || typeof body.version === 'object').toBe(true);
    expect(typeof body.bindHost).toBe('string');
    expect(typeof body.port).toBe('number');
    expect(body.port).toBe(Number(new URL(baseUrl).port));
    expect(typeof body.pid).toBe('number');
    expect(typeof body.installedPlugins).toBe('number');
    expect(body.sandboxMode).toBe(false);
    expect(body.sandbox).toEqual({ enabled: false });
    expect(body.shuttingDown).toBe(false);
    expect(body).not.toHaveProperty('namespace');
  });
});

describe('GET /api/ready', () => {
  it('returns a readiness snapshot for headless launchers', async () => {
    const resp = await fetch(`${baseUrl}/api/ready`);
    expect(resp.status).toBe(200);
    const body = (await resp.json()) as {
      ok: boolean;
      ready: boolean;
      version: unknown;
    };

    expect(body.ok).toBe(true);
    expect(body.ready).toBe(true);
    expect(typeof body.version === 'string' || typeof body.version === 'object').toBe(true);
    expect(body).not.toHaveProperty('dataDir');
    expect(body).not.toHaveProperty('sandboxMode');
    expect(body).not.toHaveProperty('sandbox');
  });
});

describe('POST /api/daemon/shutdown', () => {
  it('only accepts requests from local-daemon-allowed origins', async () => {
    // Without the local-daemon header, the route is rejected. The
    // exact rejection mode is owned by `requireLocalDaemonRequest`;
    // we just check that a default test client gets a non-2xx.
    const resp = await fetch(`${baseUrl}/api/daemon/shutdown`, {
      method:  'POST',
      headers: { origin: 'https://example.com' },
    });
    expect([401, 403, 404]).toContain(resp.status);
  });
});
