/**
 * Integration tests for Webhook endpoints.
 * Uses a real Postgres test DB and Redis test instance.
 *
 * Routes covered:
 *   POST   /api/v1/webhooks              — create a webhook subscription
 *   GET    /api/v1/webhooks              — list webhook subscriptions for org
 *   GET    /api/v1/webhooks/:id          — get a webhook subscription by ID
 *   PATCH  /api/v1/webhooks/:id          — update a webhook subscription
 *   DELETE /api/v1/webhooks/:id          — delete a webhook subscription
 */

import crypto from 'crypto';
import request from 'supertest';
import { Application } from 'express';
import { v4 as uuidv4 } from 'uuid';
import { Pool } from 'pg';

const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
  modulusLength: 2048,
  publicKeyEncoding: { type: 'spki', format: 'pem' },
  privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
});

process.env['DATABASE_URL'] =
  process.env['TEST_DATABASE_URL'] ??
  'postgresql://sentryagent:sentryagent@localhost:5432/sentryagent_idp_test';
process.env['REDIS_URL'] = process.env['TEST_REDIS_URL'] ?? 'redis://localhost:6379/1';
process.env['JWT_PRIVATE_KEY'] = privateKey;
process.env['JWT_PUBLIC_KEY'] = publicKey;
process.env['NODE_ENV'] = 'test';
process.env['DEFAULT_ORG_ID'] = 'org_system';

import { createApp } from '../../src/app';
import { signToken } from '../../src/utils/jwt';
import { closePool } from '../../src/db/pool';
import { closeRedisClient } from '../../src/cache/redis';

const ORG_ID = uuidv4();
const AGENT_ID = uuidv4();
const SCOPE = 'webhooks:manage';

function makeToken(sub: string = AGENT_ID, scope: string = SCOPE, orgId: string = ORG_ID): string {
  return signToken({ sub, client_id: sub, scope, organization_id: orgId, jti: uuidv4() }, privateKey);
}

const VALID_SUBSCRIPTION = {
  url: 'https://example.com/webhook',
  events: ['agent.created', 'agent.updated'],
  secret: 'test-secret-123',
};

describe('Webhook Endpoints Integration Tests', () => {
  let app: Application;
  let pool: Pool;
  let createdId: string;

  beforeAll(async () => {
    app = await createApp();
    pool = new Pool({ connectionString: process.env['DATABASE_URL'] });

    await pool.query(`
      CREATE TABLE IF NOT EXISTS organizations (
        organization_id VARCHAR(40) PRIMARY KEY,
        name VARCHAR(100) NOT NULL,
        plan VARCHAR(20) NOT NULL DEFAULT 'free',
        created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
        updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
      )
    `);

    await pool.query(`
      CREATE TABLE IF NOT EXISTS webhook_subscriptions (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        organization_id VARCHAR(40) NOT NULL,
        url TEXT NOT NULL,
        events JSONB NOT NULL,
        secret_hash TEXT,
        active BOOLEAN NOT NULL DEFAULT true,
        created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
        updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
      )
    `);

    await pool.query(
      `INSERT INTO organizations (organization_id, name) VALUES ($1, $2) ON CONFLICT DO NOTHING`,
      [ORG_ID, 'Test Webhook Org'],
    );
  });

  afterAll(async () => {
    await pool.query(`DELETE FROM webhook_subscriptions WHERE organization_id = $1`, [ORG_ID]);
    await pool.query(`DELETE FROM organizations WHERE organization_id = $1`, [ORG_ID]);
    await pool.end();
    await closePool();
    await closeRedisClient();
  });

  // ─── POST /webhooks ──────────────────────────────────────────────────────────

  describe('POST /api/v1/webhooks', () => {
    it('should create a webhook subscription and return 201', async () => {
      const res = await request(app)
        .post('/api/v1/webhooks')
        .set('Authorization', `Bearer ${makeToken()}`)
        .send(VALID_SUBSCRIPTION);

      expect(res.status).toBe(201);
      expect(res.body).toHaveProperty('id');
      expect(res.body.url).toBe(VALID_SUBSCRIPTION.url);
      createdId = res.body.id as string;
    });

    it('should return 401 when no token provided', async () => {
      const res = await request(app).post('/api/v1/webhooks').send(VALID_SUBSCRIPTION);
      expect(res.status).toBe(401);
    });

    it('should return 422 when url is missing', async () => {
      const res = await request(app)
        .post('/api/v1/webhooks')
        .set('Authorization', `Bearer ${makeToken()}`)
        .send({ events: ['agent.created'] });

      expect([400, 422]).toContain(res.status);
    });

    it('should return 422 when events array is empty', async () => {
      const res = await request(app)
        .post('/api/v1/webhooks')
        .set('Authorization', `Bearer ${makeToken()}`)
        .send({ url: 'https://example.com/wh', events: [] });

      expect([400, 422]).toContain(res.status);
    });
  });

  // ─── GET /webhooks ───────────────────────────────────────────────────────────

  describe('GET /api/v1/webhooks', () => {
    it('should return 200 with list of subscriptions', async () => {
      const res = await request(app)
        .get('/api/v1/webhooks')
        .set('Authorization', `Bearer ${makeToken()}`);

      expect(res.status).toBe(200);
      expect(Array.isArray(res.body.data ?? res.body)).toBe(true);
    });

    it('should return 401 when no token provided', async () => {
      const res = await request(app).get('/api/v1/webhooks');
      expect(res.status).toBe(401);
    });
  });

  // ─── GET /webhooks/:id ───────────────────────────────────────────────────────

  describe('GET /api/v1/webhooks/:id', () => {
    it('should return 200 with the subscription', async () => {
      if (!createdId) return; // depends on POST test
      const res = await request(app)
        .get(`/api/v1/webhooks/${createdId}`)
        .set('Authorization', `Bearer ${makeToken()}`);

      expect(res.status).toBe(200);
      expect(res.body.id).toBe(createdId);
    });

    it('should return 404 for non-existent subscription', async () => {
      const res = await request(app)
        .get(`/api/v1/webhooks/${uuidv4()}`)
        .set('Authorization', `Bearer ${makeToken()}`);

      expect(res.status).toBe(404);
    });

    it('should return 401 when no token provided', async () => {
      const res = await request(app).get(`/api/v1/webhooks/${uuidv4()}`);
      expect(res.status).toBe(401);
    });
  });

  // ─── DELETE /webhooks/:id ────────────────────────────────────────────────────

  describe('DELETE /api/v1/webhooks/:id', () => {
    it('should return 204 when deleting owned subscription', async () => {
      if (!createdId) return;
      const res = await request(app)
        .delete(`/api/v1/webhooks/${createdId}`)
        .set('Authorization', `Bearer ${makeToken()}`);

      expect(res.status).toBe(204);
    });

    it('should return 404 when subscription does not exist', async () => {
      const res = await request(app)
        .delete(`/api/v1/webhooks/${uuidv4()}`)
        .set('Authorization', `Bearer ${makeToken()}`);

      expect(res.status).toBe(404);
    });

    it('should return 401 when no token provided', async () => {
      const res = await request(app).delete(`/api/v1/webhooks/${uuidv4()}`);
      expect(res.status).toBe(401);
    });
  });
});
