/**
 * Integration tests for Credential Management endpoints.
 */

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';

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

function makeToken(sub: string, scope: string = 'agents:read agents:write'): string {
  return signToken({ sub, client_id: sub, scope, jti: uuidv4() }, privateKey);
}

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

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

    const migrations = [
      `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();
  });

  async function createAgent(): Promise<string> {
    const agentId = uuidv4();
    await pool.query(
      `INSERT INTO agents (agent_id, email, agent_type, version, capabilities, owner, deployment_env, status)
       VALUES ($1, $2, 'screener', '1.0.0', '{"agents:read"}', 'test', 'development', 'active')`,
      [agentId, `agent-${agentId}@test.ai`],
    );
    return agentId;
  }

  describe('POST /api/v1/agents/:agentId/credentials', () => {
    it('should generate a credential and return 201 with clientSecret', async () => {
      const agentId = await createAgent();
      const token = makeToken(agentId);

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

      expect(res.status).toBe(201);
      expect(res.body.credentialId).toBeDefined();
      expect(res.body.clientSecret).toMatch(/^sk_live_[0-9a-f]{64}$/);
      expect(res.body.status).toBe('active');
    });

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

    it('should return 403 when managing another agent credentials', async () => {
      const agentId = await createAgent();
      const otherAgent = makeToken(uuidv4()); // different sub

      const res = await request(app)
        .post(`/api/v1/agents/${agentId}/credentials`)
        .set('Authorization', `Bearer ${otherAgent}`)
        .send({});
      expect(res.status).toBe(403);
    });

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

  describe('GET /api/v1/agents/:agentId/credentials', () => {
    it('should list credentials (no clientSecret)', async () => {
      const agentId = await createAgent();
      const token = makeToken(agentId);

      // Generate first
      await request(app)
        .post(`/api/v1/agents/${agentId}/credentials`)
        .set('Authorization', `Bearer ${token}`)
        .send({});

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

      expect(res.status).toBe(200);
      expect(res.body.data).toHaveLength(1);
      expect(res.body.data[0].clientSecret).toBeUndefined();
    });
  });

  describe('POST /api/v1/agents/:agentId/credentials/:credentialId/rotate', () => {
    it('should rotate a credential and return new clientSecret', async () => {
      const agentId = await createAgent();
      const token = makeToken(agentId);

      const generated = await request(app)
        .post(`/api/v1/agents/${agentId}/credentials`)
        .set('Authorization', `Bearer ${token}`)
        .send({});

      const credentialId = generated.body.credentialId;
      const oldSecret = generated.body.clientSecret;

      const rotated = await request(app)
        .post(`/api/v1/agents/${agentId}/credentials/${credentialId}/rotate`)
        .set('Authorization', `Bearer ${token}`)
        .send({});

      expect(rotated.status).toBe(200);
      expect(rotated.body.clientSecret).toMatch(/^sk_live_[0-9a-f]{64}$/);
      expect(rotated.body.clientSecret).not.toBe(oldSecret);
    });

    it('should return 409 for rotating a revoked credential', async () => {
      const agentId = await createAgent();
      const token = makeToken(agentId);

      const generated = await request(app)
        .post(`/api/v1/agents/${agentId}/credentials`)
        .set('Authorization', `Bearer ${token}`)
        .send({});

      const credentialId = generated.body.credentialId;

      // Revoke first
      await request(app)
        .delete(`/api/v1/agents/${agentId}/credentials/${credentialId}`)
        .set('Authorization', `Bearer ${token}`);

      // Try to rotate
      const res = await request(app)
        .post(`/api/v1/agents/${agentId}/credentials/${credentialId}/rotate`)
        .set('Authorization', `Bearer ${token}`)
        .send({});

      expect(res.status).toBe(409);
      expect(res.body.code).toBe('CREDENTIAL_ALREADY_REVOKED');
    });
  });

  describe('DELETE /api/v1/agents/:agentId/credentials/:credentialId', () => {
    it('should revoke a credential and return 204', async () => {
      const agentId = await createAgent();
      const token = makeToken(agentId);

      const generated = await request(app)
        .post(`/api/v1/agents/${agentId}/credentials`)
        .set('Authorization', `Bearer ${token}`)
        .send({});

      const credentialId = generated.body.credentialId;

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

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

    it('should return 409 for revoking an already-revoked credential', async () => {
      const agentId = await createAgent();
      const token = makeToken(agentId);

      const generated = await request(app)
        .post(`/api/v1/agents/${agentId}/credentials`)
        .set('Authorization', `Bearer ${token}`)
        .send({});

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

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

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