import assert from 'node:assert/strict';
import test from 'node:test';
import { Hono } from 'hono';
import { platformSettings, teamMemberships, users, type Database } from '@pig/db';
import { createApp } from '../src/app';
import type { Principal } from '../src/lib/auth';
import { loadConfig } from '../src/lib/config';
import type { ApiEnv } from '../src/lib/mutation';
import {
  createPiggyChatRoutes,
  type PiggyChatProxyOptions,
} from '../src/routes/piggy-chat';

const principal: Principal = {
  userId: '10000000-0000-4000-8000-000000000001',
  email: 'member@example.com',
  name: 'Member',
  isPlatformAdmin: false,
  teams: [{ team: 'demand', role: 'member' }],
  via: 'jwt',
  scopes: ['read', 'write'],
};

function appFor(
  fetchImpl: typeof fetch,
  identity: Principal = principal,
  overrides: Partial<PiggyChatProxyOptions> = {},
) {
  const app = new Hono<ApiEnv>();
  app.use('*', async (context, next) => {
    context.set('principal', identity);
    await next();
  });
  app.route(
    '/',
    createPiggyChatRoutes({
      enabled: true,
      internalUrl: 'http://127.0.0.1:8931',
      internalToken: 'internal-token-with-at-least-32-characters',
      fetchImpl,
      ...overrides,
    }),
  );
  return app;
}

const ndjson = () =>
  new Response(`${JSON.stringify({ type: 'done', inputTokens: 1, outputTokens: 1 })}\n`, {
    status: 200,
    headers: { 'content-type': 'application/x-ndjson' },
  });

test('the authenticated proxy forwards bounded identity and relays NDJSON unchanged', async () => {
  let forwarded: Record<string, unknown> | undefined;
  const fetchImpl: typeof fetch = async (input, init) => {
    assert.equal(String(input), 'http://127.0.0.1:8931/internal/chat');
    assert.equal(
      new Headers(init?.headers).get('authorization'),
      'Bearer internal-token-with-at-least-32-characters',
    );
    forwarded = JSON.parse(String(init?.body)) as Record<string, unknown>;
    return new Response(
      `${JSON.stringify({ type: 'content_delta', delta: 'Scoped answer' })}\n` +
        `${JSON.stringify({ type: 'done', inputTokens: 2, outputTokens: 3 })}\n`,
      { status: 200, headers: { 'content-type': 'application/x-ndjson' } },
    );
  };
  const app = appFor(fetchImpl);
  const response = await app.request('/api/piggy/chat', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({
      message: 'Summarise this contract.',
      context: {
        type: 'contract',
        id: '20000000-0000-4000-8000-000000000002',
        label: 'Order form',
      },
    }),
  });

  assert.equal(response.status, 200);
  assert.match(response.headers.get('content-type') ?? '', /application\/x-ndjson/);
  assert.deepEqual(forwarded, {
    principalUserId: principal.userId,
    message: 'Summarise this contract.',
    context: {
      type: 'contract',
      id: '20000000-0000-4000-8000-000000000002',
      label: 'Order form',
    },
  });
  assert.equal(
    await response.text(),
    `${JSON.stringify({ type: 'content_delta', delta: 'Scoped answer' })}\n` +
      `${JSON.stringify({ type: 'done', inputTokens: 2, outputTokens: 3 })}\n`,
  );
});

test('a credential without read scope never reaches the internal service', async () => {
  let fetched = false;
  const app = appFor(
    async () => {
      fetched = true;
      return new Response();
    },
    { ...principal, scopes: ['write'] },
  );
  const response = await app.request('/api/piggy/chat', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ message: 'Read the book.' }),
  });
  assert.equal(response.status, 403);
  assert.equal(fetched, false);
});

test('a docked page context reaches the chat service unaltered', async () => {
  let forwarded: Record<string, unknown> | undefined;
  const app = appFor(async (_input, init) => {
    forwarded = JSON.parse(String(init?.body)) as Record<string, unknown>;
    return ndjson();
  });
  const response = await app.request('/api/piggy/chat', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({
      message: 'What is idle?',
      context: { type: 'page', route: '/capacity', label: 'Capacity' },
    }),
  });

  assert.equal(response.status, 200);
  assert.deepEqual(forwarded?.context, {
    type: 'page',
    route: '/capacity',
    label: 'Capacity',
  });
});

// A page context carries no record, so admitting one would put a nonsense
// shape in front of the model rather than failing at the boundary.
test('a page context may not smuggle a record id, and an unknown route is refused', async () => {
  let fetched = false;
  const app = appFor(async () => {
    fetched = true;
    return ndjson();
  });

  for (const context of [
    { type: 'page', route: '/not-a-page' },
    { type: 'page', route: '/margin', id: '20000000-0000-4000-8000-000000000002' },
    { type: 'page' },
  ]) {
    const response = await app.request('/api/piggy/chat', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ message: 'Where are we?', context }),
    });
    assert.equal(response.status, 400);
    assert.equal(((await response.json()) as { code: string }).code, 'invalid_request');
  }
  assert.equal(fetched, false);
});

test('the stored admin toggle disables chat without the environment changing', async () => {
  let fetched = false;
  let piggyEnabled = true;
  const app = appFor(
    async () => {
      fetched = true;
      return ndjson();
    },
    principal,
    { resolvePiggyEnabled: async () => piggyEnabled },
  );

  assert.deepEqual(await (await app.request('/api/piggy/status')).json(), {
    enabled: true,
    canUse: true,
  });

  piggyEnabled = false;
  assert.deepEqual(await (await app.request('/api/piggy/status')).json(), {
    enabled: false,
    canUse: false,
  });
  const response = await app.request('/api/piggy/chat', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ message: 'Where are we?' }),
  });
  assert.equal(response.status, 503);
  assert.equal(fetched, false);
});

// Losing the settings row must degrade to the environment gate. A dock on
// every page turns one failed query into a site-wide outage otherwise.
test('an unreadable settings row falls back to the environment gate', async () => {
  const app = appFor(async () => ndjson(), principal, {
    resolvePiggyEnabled: async () => {
      throw new Error('platform settings unavailable');
    },
  });
  assert.deepEqual(await (await app.request('/api/piggy/status')).json(), {
    enabled: true,
    canUse: true,
  });
});

test('the environment gate still overrides a stored toggle that says yes', async () => {
  const app = appFor(async () => ndjson(), principal, {
    enabled: false,
    resolvePiggyEnabled: async () => true,
  });
  assert.deepEqual(await (await app.request('/api/piggy/status')).json(), {
    enabled: false,
    canUse: false,
  });
});

// ---------------------------------------------------------------------------
// Composition
// ---------------------------------------------------------------------------

/**
 * Enough of a Database to authenticate a development request and read the
 * settings row, and nothing more.
 *
 * Predicates are ignored on purpose: this asserts a WIRING, and a fake that
 * tried to execute SQL semantics would be a worse test of the wiring and a
 * pointless test of Drizzle. Anything the app queries beyond these three
 * tables comes back empty, which is what an untouched deployment looks like.
 */
function stubDatabase(store: { piggyEnabled: boolean }): Database {
  const rowsFor = (table: unknown): Record<string, unknown>[] => {
    if (table === users) {
      return [
        {
          id: principal.userId,
          email: principal.email,
          name: principal.name,
          isPlatformAdmin: false,
          deactivatedAt: null,
        },
      ];
    }
    if (table === teamMemberships) return [{ team: 'demand', role: 'member' }];
    if (table === platformSettings) return [{ piggyEnabled: store.piggyEnabled }];
    return [];
  };

  const query = (rows: Record<string, unknown>[]): Record<string, unknown> => {
    const chain: Record<string, unknown> = {
      from: (table: unknown) => query(rowsFor(table)),
      where: () => chain,
      limit: () => chain,
      orderBy: () => chain,
      innerJoin: () => chain,
      leftJoin: () => chain,
      values: () => chain,
      onConflictDoNothing: () => chain,
      returning: () => chain,
      then: (resolve: (value: Record<string, unknown>[]) => unknown) => resolve(rows),
    };
    return chain;
  };

  return {
    select: () => query([]),
    insert: (table: unknown) => query(rowsFor(table)),
  } as unknown as Database;
}

/**
 * The regression this file could not previously catch.
 *
 * The three toggle tests above build the routes themselves and inject a
 * resolver, so every one of them stayed green through a release in which
 * `createApp` never passed one — turning Piggy off in the admin UI did nothing
 * at all in production. Only a request through the composed app proves the
 * stored setting is consulted, so this one goes through `createApp`.
 */
test('createApp wires the stored toggle into the chat routes', async () => {
  const store = { piggyEnabled: false };
  const config = loadConfig({
    NODE_ENV: 'development',
    DATABASE_URL: 'postgres://pig:pig@localhost:5432/pig',
    PIGGY_ENABLED: 'true',
    PIGGY_INTERNAL_URL: 'http://127.0.0.1:8931',
    PIGGY_INTERNAL_TOKEN: 'internal-token-with-at-least-32-characters',
  });
  // Null provider is the development path: no token, principal comes from the
  // first user in the table. What is under test is the toggle, not the auth.
  const app = createApp(config, stubDatabase(store), null);

  assert.equal(config.PIGGY_ENABLED, true);
  assert.deepEqual(await (await app.request('/api/piggy/status')).json(), {
    enabled: false,
    canUse: false,
  });

  store.piggyEnabled = true;
  assert.deepEqual(await (await app.request('/api/piggy/status')).json(), {
    enabled: true,
    canUse: true,
  });
});
