import { strict as assert } from 'node:assert';
import { createHmac } from 'node:crypto';
import { describe, it } from 'node:test';
import type { Database } from '@pig/db';
import type { Principal } from '../src/lib/auth';
import { AuthError } from '../src/lib/auth';
import { executeMutation } from '../src/lib/mutation';
import {
  createSlackLinkMutationDefinition,
  parseSlashRequirement,
  verifySlackRequest,
} from '../src/routes/slack';
import { NotificationDeliveryError } from '../src/services/notifier';
import { SlackNotifier, slackClientMessageId } from '../src/services/slack';

const NOW = 1_786_579_200;
const SECRET = 'test-signing-secret';

function signature(timestamp: number, body: string): string {
  return `v0=${createHmac('sha256', SECRET).update(`v0:${timestamp}:${body}`).digest('hex')}`;
}

describe('Slack request verification', () => {
  it('accepts the exact signed bytes and rejects tampering', () => {
    const body = 'team_id=T1&channel_id=C1&text=8+H100_80GB';
    const signed = signature(NOW, body);
    assert.equal(verifySlackRequest(SECRET, String(NOW), signed, body, NOW), true);
    assert.equal(verifySlackRequest(SECRET, String(NOW), signed, `${body}+fabric`, NOW), false);
  });

  it('rejects replayed requests outside Slack\'s five-minute window', () => {
    const body = 'team_id=T1&channel_id=C1&text=8+H100_80GB';
    const old = NOW - 301;
    assert.equal(verifySlackRequest(SECRET, String(old), signature(old, body), body, NOW), false);
  });
});

describe('Slack capacity command', () => {
  it('turns transport syntax into a CapacityService requirement without matching in the handler', () => {
    assert.deepEqual(
      parseSlashRequirement('8 H100_80GB hours=640 max=2.50 fabric tier=secure_cloud'),
      {
        gpuCount: 8,
        gpuType: 'H100_80GB',
        totalGpuHours: 640,
        maxPricePerGpuHourCents: 250,
        requiresHighSpeedInterconnect: true,
        minSecurityTier: 'secure_cloud',
      },
    );
    assert.equal(parseSlashRequirement('eight H100_80GB'), null);
  });
});

describe('Slack channel link authorization', () => {
  it('denies non-admins before reading link input or opening a transaction', async () => {
    const events: string[] = [];
    const principal: Principal = {
      userId: '00000000-0000-4000-8000-000000000001',
      email: 'seller@example.com',
      name: 'Seller',
      isPlatformAdmin: false,
      teams: [{ team: 'demand', role: 'admin' }],
      via: 'jwt',
      scopes: ['read', 'write'],
    };
    const db = {
      transaction: async () => {
        events.push('transaction');
      },
    } as unknown as Database;

    await assert.rejects(
      executeMutation(
        db,
        principal,
        async () => {
          events.push('body');
          return {};
        },
        createSlackLinkMutationDefinition(),
      ),
      (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
    );
    assert.deepEqual(events, []);
  });
});

describe('Slack delivery decisions', () => {
  it('reuses a deterministic client message id across retries', async () => {
    const bodies: Record<string, unknown>[] = [];
    const notifier = new SlackNotifier({
      botToken: 'xoxb-test',
      fetchImpl: async (_input, init) => {
        bodies.push(JSON.parse(String(init?.body)) as Record<string, unknown>);
        return Response.json({ ok: true, ts: '123.456' });
      },
    });
    const envelope = {
      idempotencyKey: 'slack:link:event',
      destination: 'C123',
      notification: {
        kind: 'stage_change' as const,
        accountId: '10000000-0000-4000-8000-000000000001',
        dealId: '20000000-0000-4000-8000-000000000002',
        dealSide: 'demand' as const,
        dealName: 'Reserved H100 cluster',
        fromStage: 'proposal',
        toStage: 'procurement',
        changedAt: '2026-08-12T12:00:00.000Z',
      },
    };
    await notifier.send(envelope);
    await notifier.send(envelope);
    assert.equal(bodies[0]?.client_msg_id, slackClientMessageId(envelope.idempotencyKey));
    assert.equal(bodies[1]?.client_msg_id, bodies[0]?.client_msg_id);
  });

  it('marks rate limits retryable and honours Slack retry-after', async () => {
    const notifier = new SlackNotifier({
      botToken: 'xoxb-test',
      fetchImpl: async () =>
        new Response('', { status: 429, headers: { 'retry-after': '7' } }),
    });
    await assert.rejects(
      notifier.send({
        idempotencyKey: 'one',
        destination: 'C1',
        notification: {
          kind: 'idle_capacity',
          accountId: '10000000-0000-4000-8000-000000000001',
          commitmentId: '20000000-0000-4000-8000-000000000002',
          commitmentName: 'Eight H100s',
          gpuType: 'H100_80GB',
          idleGpuHours: 640,
          idleCostCents: 120_000,
          utilisation: 0,
          observedAt: '2026-08-12T12:00:00.000Z',
        },
      }),
      (error: unknown) =>
        error instanceof NotificationDeliveryError &&
        error.retryable &&
        error.retryAfterMs === 7_000,
    );
  });
});
