/**
 * Integration tests for Agent Registry endpoints.
 * Uses a real Postgres test DB and Redis test instance.
 */

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

// Set test environment variables before importing app
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';

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

const AGENT_ID = uuidv4();
const SCOPE = 'agents:read agents:write';

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

describe('Agent Registry Integration Tests', () => {
  let app: Application;
  let pool: Pool;

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

    // Run migrations
    const migrations = [
      `CREATE TABLE IF NOT EXISTS schema_migrations (name VARCHAR(255) PRIMARY KEY, applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW())`,
      `CREATE TABLE IF NOT EXISTS agents (
        agent_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        email VARCHAR(255) NOT NULL UNIQUE,
        agent_type VARCHAR(32) NOT NULL,
        version VARCHAR(64) NOT NULL,
        capabilities TEXT[] NOT NULL DEFAULT '{}',
        owner VARCHAR(128) NOT NULL,
        deployment_env VARCHAR(16) NOT NULL,
        status VARCHAR(24) NOT NULL DEFAULT 'active',
        created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
        updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
      )`,
      `CREATE TABLE IF NOT EXISTS credentials (
        credential_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        client_id UUID NOT NULL,
        secret_hash VARCHAR(255) NOT NULL,
        status VARCHAR(16) NOT NULL DEFAULT 'active',
        created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
        expires_at TIMESTAMPTZ,
        revoked_at TIMESTAMPTZ
      )`,
      `CREATE TABLE IF NOT EXISTS audit_events (
        event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        agent_id UUID NOT NULL,
        action VARCHAR(32) NOT NULL,
        outcome VARCHAR(16) NOT NULL,
        ip_address VARCHAR(64) NOT NULL,
        user_agent TEXT NOT NULL,
        metadata JSONB NOT NULL DEFAULT '{}',
        timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
      )`,
      `CREATE TABLE IF NOT EXISTS token_revocations (
        jti UUID PRIMARY KEY,
        expires_at TIMESTAMPTZ NOT NULL,
        revoked_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
      )`,
    ];

    for (const sql of migrations) {
      await pool.query(sql);
    }
  });

  afterEach(async () => {
    await pool.query('DELETE FROM audit_events');
    await pool.query('DELETE FROM credentials');
    await pool.query('DELETE FROM agents');
  });

  afterAll(async () => {
    await pool.end();
    await closePool();
    await closeRedisClient();
  });

  const validAgent = {
    email: 'test-agent@sentryagent.ai',
    agentType: 'screener',
    version: '1.0.0',
    capabilities: ['resume:read'],
    owner: 'test-team',
    deploymentEnv: 'development',
  };

  describe('POST /api/v1/agents', () => {
    it('should register a new agent and return 201', async () => {
      const token = makeToken();
      const res = await request(app)
        .post('/api/v1/agents')
        .set('Authorization', `Bearer ${token}`)
        .send(validAgent);

      expect(res.status).toBe(201);
      expect(res.body.agentId).toBeDefined();
      expect(res.body.email).toBe(validAgent.email);
      expect(res.body.status).toBe('active');
    });

    it('should return 401 without a token', async () => {
      const res = await request(app).post('/api/v1/agents').send(validAgent);
      expect(res.status).toBe(401);
    });

    it('should return 400 for invalid request body', async () => {
      const token = makeToken();
      const res = await request(app)
        .post('/api/v1/agents')
        .set('Authorization', `Bearer ${token}`)
        .send({ email: 'not-an-email' });
      expect(res.status).toBe(400);
    });

    it('should return 409 for duplicate email', async () => {
      const token = makeToken();
      await request(app)
        .post('/api/v1/agents')
        .set('Authorization', `Bearer ${token}`)
        .send(validAgent);

      const res = await request(app)
        .post('/api/v1/agents')
        .set('Authorization', `Bearer ${token}`)
        .send(validAgent);
      expect(res.status).toBe(409);
      expect(res.body.code).toBe('AGENT_ALREADY_EXISTS');
    });
  });

  describe('GET /api/v1/agents', () => {
    it('should return a paginated list of agents', async () => {
      const token = makeToken();
      await request(app)
        .post('/api/v1/agents')
        .set('Authorization', `Bearer ${token}`)
        .send(validAgent);

      const res = await request(app)
        .get('/api/v1/agents')
        .set('Authorization', `Bearer ${token}`);

      expect(res.status).toBe(200);
      expect(res.body.data).toBeInstanceOf(Array);
      expect(res.body.total).toBeGreaterThanOrEqual(1);
      expect(res.body.page).toBe(1);
    });

    it('should support filtering by status', async () => {
      const token = makeToken();
      await request(app)
        .post('/api/v1/agents')
        .set('Authorization', `Bearer ${token}`)
        .send(validAgent);

      const res = await request(app)
        .get('/api/v1/agents?status=active')
        .set('Authorization', `Bearer ${token}`);

      expect(res.status).toBe(200);
      res.body.data.forEach((a: { status: string }) => expect(a.status).toBe('active'));
    });

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

  describe('GET /api/v1/agents/:agentId', () => {
    it('should return an agent by ID', async () => {
      const token = makeToken();
      const created = await request(app)
        .post('/api/v1/agents')
        .set('Authorization', `Bearer ${token}`)
        .send(validAgent);

      const res = await request(app)
        .get(`/api/v1/agents/${created.body.agentId}`)
        .set('Authorization', `Bearer ${token}`);

      expect(res.status).toBe(200);
      expect(res.body.agentId).toBe(created.body.agentId);
    });

    it('should return 404 for unknown agentId', async () => {
      const token = makeToken();
      const res = await request(app)
        .get(`/api/v1/agents/${uuidv4()}`)
        .set('Authorization', `Bearer ${token}`);
      expect(res.status).toBe(404);
    });
  });

  describe('PATCH /api/v1/agents/:agentId', () => {
    it('should update the agent and return 200', async () => {
      const token = makeToken();
      const created = await request(app)
        .post('/api/v1/agents')
        .set('Authorization', `Bearer ${token}`)
        .send(validAgent);

      const res = await request(app)
        .patch(`/api/v1/agents/${created.body.agentId}`)
        .set('Authorization', `Bearer ${token}`)
        .send({ version: '2.0.0' });

      expect(res.status).toBe(200);
      expect(res.body.version).toBe('2.0.0');
    });

    it('should return 404 for unknown agentId', async () => {
      const token = makeToken();
      const res = await request(app)
        .patch(`/api/v1/agents/${uuidv4()}`)
        .set('Authorization', `Bearer ${token}`)
        .send({ version: '2.0.0' });
      expect(res.status).toBe(404);
    });
  });

  describe('DELETE /api/v1/agents/:agentId', () => {
    it('should decommission the agent and return 204', async () => {
      const token = makeToken();
      const created = await request(app)
        .post('/api/v1/agents')
        .set('Authorization', `Bearer ${token}`)
        .send(validAgent);

      const res = await request(app)
        .delete(`/api/v1/agents/${created.body.agentId}`)
        .set('Authorization', `Bearer ${token}`);

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

    it('should return 409 if already decommissioned', async () => {
      const token = makeToken();
      const created = await request(app)
        .post('/api/v1/agents')
        .set('Authorization', `Bearer ${token}`)
        .send(validAgent);

      await request(app)
        .delete(`/api/v1/agents/${created.body.agentId}`)
        .set('Authorization', `Bearer ${token}`);

      const res = await request(app)
        .delete(`/api/v1/agents/${created.body.agentId}`)
        .set('Authorization', `Bearer ${token}`);

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