import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import * as os from 'node:os';
import { createGatewayServers } from '../../apps/gateway/src/server.js';
import { ConfigLoader } from '@zyphon/config-loader';
import { DailyLogWriter, DailyLogReader } from '@zyphon/logger';
import { CredentialVault } from '../../apps/gateway/src/core/credential-vault.js';
import { QuotaManager } from '../../apps/gateway/src/core/quota-manager.js';
import { ApplicationRegistry } from '../../apps/gateway/src/core/application-registry.js';
import { CircuitBreakerManager } from '../../apps/gateway/src/core/circuit-breaker.js';
import { RoutingEngine } from '../../apps/gateway/src/core/routing-engine.js';
import { ProviderRegistry } from '../../apps/gateway/src/core/provider-registry.js';
import { IdempotencyManager } from '../../apps/gateway/src/core/idempotency-manager.js';
import { EventBus } from '../../apps/gateway/src/realtime/event-bus.js';
import { MetricsCollector } from '../../apps/gateway/src/core/metrics.js';
import { DispatchService } from '../../apps/gateway/src/messaging/dispatch-service.js';
import { AdminAuthManager } from '../../apps/gateway/src/auth/admin-auth.js';
import { MockProvider } from '@zyphon/provider-mock';
import { resolveMasterKey } from '@zyphon/crypto';
import { adminMutatingHeaders, adminReadHeaders } from '../helpers/admin-headers.js';

describe('Admin security: cookies, CSRF, RBAC, secret isolation', () => {
  let tmpDir: string;
  let servers: ReturnType<typeof createGatewayServers>;
  let quotaManager: QuotaManager;
  let eventBus: EventBus;
  let adminAuth: AdminAuthManager;
  let adminToken: string;
  let csrfToken: string;

  beforeEach(async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'zyphon-security-test-'));
    const configDir = path.join(tmpDir, 'config');
    const stateDir = path.join(tmpDir, 'data', 'state');
    const logsDir = path.join(tmpDir, 'data', 'logs');

    await fs.mkdir(configDir, { recursive: true });
    await fs.mkdir(stateDir, { recursive: true });
    await fs.mkdir(logsDir, { recursive: true });

    const masterKey = resolveMasterKey('0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef');
    const vault = new CredentialVault(path.join(stateDir, 'vault.enc.json'), masterKey);
    quotaManager = new QuotaManager(stateDir);
    const appRegistry = new ApplicationRegistry([], quotaManager, stateDir);
    const circuitBreaker = new CircuitBreakerManager();
    const routingEngine = new RoutingEngine(
      {
        default: { email: ['mock-email'], sms: ['mock-sms'], whatsapp: ['mock-whatsapp'] },
        retry: { max_attempts: 2, backoff_ms: 10 },
        timeout_ms: { email: 1000, sms: 1000, whatsapp: 1000 },
      },
      circuitBreaker
    );

    const providerRegistry = new ProviderRegistry(vault);
    providerRegistry.registerAdapter(new MockProvider({ id: 'mock-email', channel: 'email' }));

    const logWriter = new DailyLogWriter({ logsDir });
    const logReader = new DailyLogReader(logsDir);
    eventBus = new EventBus(100);
    const idempotencyManager = new IdempotencyManager(5, 100);
    const metrics = new MetricsCollector();

    const dispatchService = new DispatchService({
      appRegistry,
      routingEngine,
      vault,
      circuitBreaker,
      idempotencyManager,
      providerRegistry,
      logWriter,
      eventBus,
    });

    adminAuth = new AdminAuthManager('correct-admin-password');
    const configLoader = new ConfigLoader(configDir, stateDir);

    servers = createGatewayServers({
      dispatchService,
      appRegistry,
      routingEngine,
      vault,
      circuitBreaker,
      providerRegistry,
      eventBus,
      adminAuth,
      metrics,
      logWriter,
      logReader,
      configLoader,
    });

    const loginRes = await servers.adminApp.inject({
      method: 'POST',
      url: '/admin/auth/login',
      payload: { password: 'correct-admin-password' },
    });
    expect(loginRes.statusCode).toBe(200);
    const loginBody = JSON.parse(loginRes.body) as { token: string; csrfToken: string };
    adminToken = loginBody.token;
    csrfToken = loginBody.csrfToken;

    const setCookie = loginRes.headers['set-cookie'];
    const cookieLines = Array.isArray(setCookie) ? setCookie : setCookie ? [setCookie] : [];
    const sessionCookie = cookieLines.find((c) => c.startsWith('zyphon_session='));
    expect(sessionCookie).toBeDefined();
    expect(sessionCookie).toContain('SameSite=Strict');
    expect(sessionCookie).toContain('HttpOnly');
  });

  afterEach(async () => {
    quotaManager.stop();
    eventBus.stop();
    await servers.publicApp.close();
    await servers.adminApp.close();
    await fs.rm(tmpDir, { recursive: true, force: true });
  });

  it('allows mutating admin requests with admin session token', async () => {
    const res = await servers.adminApp.inject({
      method: 'POST',
      url: '/admin/applications',
      headers: adminReadHeaders(adminToken),
      payload: { id: 'admin-app-test', name: 'Admin App', channels: ['email'] },
    });
    expect(res.statusCode).toBe(201);
  });

  it('allows mutating requests with matching CSRF cookie and header', async () => {
    const res = await servers.adminApp.inject({
      method: 'POST',
      url: '/admin/applications',
      headers: adminMutatingHeaders(adminToken, csrfToken),
      payload: { id: 'with-csrf', name: 'With CSRF', channels: ['email'] },
    });
    expect(res.statusCode).toBe(201);
  });

  it('blocks viewer sessions from mutating routes', async () => {
    const viewerToken = adminAuth.createSession('viewer');
    const viewerCsrf = 'viewer-csrf-token-fixed-for-test';

    const res = await servers.adminApp.inject({
      method: 'POST',
      url: '/admin/applications',
      headers: adminMutatingHeaders(viewerToken, viewerCsrf),
      payload: { id: 'viewer-blocked', name: 'Viewer Blocked' },
    });
    expect(res.statusCode).toBe(403);
    expect(JSON.parse(res.body).error.code).toBe('FORBIDDEN');
  });

  it('never returns vault secrets in admin API responses', async () => {
    const secretValue = 'SG.leak_test_secret_value_999';
    const putRes = await servers.adminApp.inject({
      method: 'PUT',
      url: '/admin/vault/leak-test',
      headers: adminMutatingHeaders(adminToken, csrfToken),
      payload: { secrets: { api_key: secretValue } },
    });
    expect(putRes.statusCode).toBe(200);
    expect(putRes.body).not.toContain(secretValue);

    const listRes = await servers.adminApp.inject({
      method: 'GET',
      url: '/admin/vault',
      headers: adminReadHeaders(adminToken),
    });
    expect(listRes.statusCode).toBe(200);
    expect(listRes.body).not.toContain(secretValue);
  });
});
