/**
 * Integration tests for OIDC Trust Policy endpoints.
 * Uses a real Postgres test DB and Redis test instance.
 *
 * Routes covered:
 *   POST   /api/v1/oidc/trust-policies           — create a trust policy
 *   GET    /api/v1/oidc/trust-policies/:agentId  — list trust policies for an agent
 *   DELETE /api/v1/oidc/trust-policies/:id       — delete a trust policy
 */

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 = 'agents:write';

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);
}

describe('OIDC Trust Policy Endpoints Integration Tests', () => {
  let app: Application;
  let pool: Pool;
  let createdPolicyId: 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 agents (
        agent_id VARCHAR(40) PRIMARY KEY,
        organization_id VARCHAR(40) NOT NULL,
        email VARCHAR(255) NOT NULL UNIQUE,
        agent_type VARCHAR(50) NOT NULL,
        version VARCHAR(20) NOT NULL,
        capabilities TEXT[] NOT NULL DEFAULT '{}',
        owner VARCHAR(100) NOT NULL,
        deployment_env VARCHAR(50) NOT NULL,
        status VARCHAR(20) NOT NULL DEFAULT 'active',
        is_public BOOLEAN NOT NULL DEFAULT false,
        did TEXT,
        did_created_at TIMESTAMPTZ,
        created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
        updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
      )
    `);

    await pool.query(`
      CREATE TABLE IF NOT EXISTS oidc_trust_policies (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        provider VARCHAR(20) NOT NULL,
        repository VARCHAR(255) NOT NULL,
        branch VARCHAR(100),
        agent_id VARCHAR(40) NOT NULL,
        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 OIDC Org'],
    );

    await pool.query(
      `INSERT INTO agents
         (agent_id, organization_id, email, agent_type, version, capabilities, owner, deployment_env)
       VALUES ($1, $2, $3, 'ci-runner', 'v1.0.0', '{}', 'ci-team', 'production')
       ON CONFLICT DO NOTHING`,
      [AGENT_ID, ORG_ID, `oidc-agent-${AGENT_ID}@test.com`],
    );
  });

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

  // ─── POST /oidc/trust-policies ───────────────────────────────────────────────

  describe('POST /api/v1/oidc/trust-policies', () => {
    it('should create a trust policy and return 201', async () => {
      const res = await request(app)
        .post('/api/v1/oidc/trust-policies')
        .set('Authorization', `Bearer ${makeToken()}`)
        .send({
          provider: 'github',
          repository: 'acme/my-repo',
          branch: 'main',
          agentId: AGENT_ID,
        });

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

    it('should return 401 when no token provided', async () => {
      const res = await request(app)
        .post('/api/v1/oidc/trust-policies')
        .send({ provider: 'github', repository: 'acme/repo', agentId: AGENT_ID });

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

    it('should return 422 for invalid provider', async () => {
      const res = await request(app)
        .post('/api/v1/oidc/trust-policies')
        .set('Authorization', `Bearer ${makeToken()}`)
        .send({ provider: 'gitlab', repository: 'acme/repo', agentId: AGENT_ID });

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

    it('should return 422 for malformed repository', async () => {
      const res = await request(app)
        .post('/api/v1/oidc/trust-policies')
        .set('Authorization', `Bearer ${makeToken()}`)
        .send({ provider: 'github', repository: 'no-slash', agentId: AGENT_ID });

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

  // ─── GET /oidc/trust-policies/:agentId ──────────────────────────────────────

  describe('GET /api/v1/oidc/trust-policies/:agentId', () => {
    it('should return 200 with list of trust policies', async () => {
      const res = await request(app)
        .get(`/api/v1/oidc/trust-policies/${AGENT_ID}`)
        .set('Authorization', `Bearer ${makeToken()}`);

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

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

  // ─── DELETE /oidc/trust-policies/:id ────────────────────────────────────────

  describe('DELETE /api/v1/oidc/trust-policies/:id', () => {
    it('should return 204 when deleting an existing policy', async () => {
      if (!createdPolicyId) return;
      const res = await request(app)
        .delete(`/api/v1/oidc/trust-policies/${createdPolicyId}`)
        .set('Authorization', `Bearer ${makeToken()}`);

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

    it('should return 404 when policy does not exist', async () => {
      const res = await request(app)
        .delete(`/api/v1/oidc/trust-policies/${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/oidc/trust-policies/${uuidv4()}`);
      expect(res.status).toBe(401);
    });
  });
});
