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 API Integration', () => {
  let tmpDir: string;
  let servers: ReturnType<typeof createGatewayServers>;
  let quotaManager: QuotaManager;
  let eventBus: EventBus;
  let adminToken: string;
  let csrfToken: string;

  beforeEach(async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'zyphon-admin-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 adminAuth = new AdminAuthManager('correct-admin-password');
    const configLoader = new ConfigLoader(configDir, stateDir);

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

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

    // Login as admin
    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;
  });

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

  it('rejects access to protected admin routes without token', async () => {
    const res = await servers.adminApp.inject({
      method: 'GET',
      url: '/admin/applications',
    });

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

  it('allows access to protected admin routes with valid token', async () => {
    const res = await servers.adminApp.inject({
      method: 'GET',
      url: '/admin/applications',
      headers: adminReadHeaders(adminToken),
    });

    expect(res.statusCode).toBe(200);
    expect(JSON.parse(res.body)).toEqual([]);
  });

  it('creates and manages applications and generates API keys', async () => {
    // 1. Create app
    const createRes = await servers.adminApp.inject({
      method: 'POST',
      url: '/admin/applications',
      headers: adminMutatingHeaders(adminToken, csrfToken),
      payload: {
        id: 'crm_app',
        name: 'CRM System',
        channels: ['email'],
        quota: { per_min: 60, per_day: 1000 },
      },
    });
    expect(createRes.statusCode).toBe(201);
    const createdApp = JSON.parse(createRes.body);
    expect(createdApp.id).toBe('crm_app');

    // 2. Generate API key
    const keyRes = await servers.adminApp.inject({
      method: 'POST',
      url: '/admin/applications/crm_app/keys',
      headers: adminMutatingHeaders(adminToken, csrfToken),
    });
    expect(keyRes.statusCode).toBe(201);
    const keyData = JSON.parse(keyRes.body);
    expect(keyData.api_key.startsWith('zy_')).toBe(true);

    // 3. List applications
    const listRes = await servers.adminApp.inject({
      method: 'GET',
      url: '/admin/applications',
      headers: adminReadHeaders(adminToken),
    });
    expect(listRes.statusCode).toBe(200);
    const list = JSON.parse(listRes.body);
    expect(list.length).toBe(1);
    expect(list[0].keys.length).toBe(1);
    expect(list[0].keys[0].key_prefix).toBeDefined();
    // Raw secret API key must never be present in application listings!
    expect(JSON.stringify(list)).not.toContain(keyData.api_key);
  });

  it('updates encrypted vault secrets and never reveals raw secrets', async () => {
    // Put secret in vault
    const putRes = await servers.adminApp.inject({
      method: 'PUT',
      url: '/admin/vault/sendgrid-prod',
      headers: adminMutatingHeaders(adminToken, csrfToken),
      payload: {
        secrets: { api_key: 'SG.secret_key_12345' },
        meta: { host: 'api.sendgrid.com' },
      },
    });
    expect(putRes.statusCode).toBe(200);

    // List vault metadata
    const listRes = await servers.adminApp.inject({
      method: 'GET',
      url: '/admin/vault',
      headers: adminReadHeaders(adminToken),
    });
    expect(listRes.statusCode).toBe(200);
    const entries = JSON.parse(listRes.body);
    expect(entries.length).toBe(1);
    expect(entries[0].provider_id).toBe('sendgrid-prod');
    expect(entries[0].meta.host).toBe('api.sendgrid.com');

    // CRITICAL: Raw secret must NEVER be returned in any vault API response!
    expect(JSON.stringify(entries)).not.toContain('SG.secret_key_12345');
  });

  it('returns metrics summary and prometheus format', async () => {
    const jsonRes = await servers.adminApp.inject({
      method: 'GET',
      url: '/admin/metrics',
      headers: adminReadHeaders(adminToken),
    });
    expect(jsonRes.statusCode).toBe(200);
    const jsonMetrics = JSON.parse(jsonRes.body);
    expect(jsonMetrics.total).toBeDefined();

    const promRes = await servers.adminApp.inject({
      method: 'GET',
      url: '/metrics',
    });
    expect(promRes.statusCode).toBe(200);
    expect(promRes.body).toContain('zyphon_messages_total');
  });

  it('provides real non-fabricated stats and empty message list when fresh', async () => {
    // 1. Stats on fresh install
    const statsRes = await servers.adminApp.inject({
      method: 'GET',
      url: '/admin/stats',
      headers: adminReadHeaders(adminToken),
    });
    expect(statsRes.statusCode).toBe(200);
    const stats = JSON.parse(statsRes.body);
    expect(stats.sent_today).toBe(0);
    expect(stats.failed_today).toBe(0);
    // CRITICAL: Success rate is null (displayed as "—" in UI) with 0 data, NEVER fabricated "+14%"
    expect(stats.success_rate).toBeNull();
    expect(stats.active_apps).toBe(0);

    // 2. Messages list
    const msgsRes = await servers.adminApp.inject({
      method: 'GET',
      url: '/admin/messages',
      headers: adminReadHeaders(adminToken),
    });
    expect(msgsRes.statusCode).toBe(200);
    expect(JSON.parse(msgsRes.body)).toEqual([]);
  });

  it('manages providers lifecycle, test send, and channel routing order', async () => {
    // 1. Create a provider with secrets
    const createProvRes = await servers.adminApp.inject({
      method: 'POST',
      url: '/admin/providers',
      headers: adminMutatingHeaders(adminToken, csrfToken),
      payload: {
        id: 'smtp-corp',
        channel: 'email',
        type: 'smtp',
        name: 'Corporate SMTP',
        config: { host: 'smtp.corp.com', port: 587, from: 'noreply@corp.com' },
        secrets: { password: 'SuperSecretSmtpPassword123' },
      },
    });
    expect(createProvRes.statusCode).toBe(201);
    const createdProv = JSON.parse(createProvRes.body);
    expect(createdProv.id).toBe('smtp-corp');
    expect(createdProv.name).toBe('Corporate SMTP');
    // Secret must NOT be in response
    expect(JSON.stringify(createdProv)).not.toContain('SuperSecretSmtpPassword123');

    // 2. List providers - verify secret isolation
    const listProvRes = await servers.adminApp.inject({
      method: 'GET',
      url: '/admin/providers',
      headers: adminReadHeaders(adminToken),
    });
    expect(listProvRes.statusCode).toBe(200);
    const providers = JSON.parse(listProvRes.body);
    const smtp = providers.find((p: any) => p.id === 'smtp-corp');
    expect(smtp).toBeDefined();
    expect(smtp.status).toBe('Connected');
    expect(smtp.config.host).toBe('smtp.corp.com');
    expect(smtp.secret_set).toBe(true);
    // CRITICAL: Zero secrets in provider list response
    expect(JSON.stringify(providers)).not.toContain('SuperSecretSmtpPassword123');

    // 3. Test send through provider
    const testSendRes = await servers.adminApp.inject({
      method: 'POST',
      url: '/admin/providers/smtp-corp/test-send',
      headers: adminMutatingHeaders(adminToken, csrfToken),
      payload: { to: 'admin@corp.com' },
    });
    expect(testSendRes.statusCode).toBe(200);
    const testSendResult = JSON.parse(testSendRes.body);
    expect(testSendResult.message_id).toBeDefined();

    // 4. Update channel sending order
    const orderRes = await servers.adminApp.inject({
      method: 'PUT',
      url: '/admin/channels/email/order',
      headers: adminMutatingHeaders(adminToken, csrfToken),
      payload: { order: ['smtp-corp', 'mock-email'] },
    });
    expect(orderRes.statusCode).toBe(200);
    expect(JSON.parse(orderRes.body).order).toEqual(['smtp-corp', 'mock-email']);

    // 5. Delete provider
    const delRes = await servers.adminApp.inject({
      method: 'DELETE',
      url: '/admin/providers/smtp-corp',
      headers: adminMutatingHeaders(adminToken, csrfToken),
    });
    expect(delRes.statusCode).toBe(200);
  });

  it('manages settings and application key rotation', async () => {
    // 1. Get settings
    const getSetRes = await servers.adminApp.inject({
      method: 'GET',
      url: '/admin/settings',
      headers: adminReadHeaders(adminToken),
    });
    expect(getSetRes.statusCode).toBe(200);
    const settings = JSON.parse(getSetRes.body);
    expect(settings.retry).toBeDefined();
    expect(settings.circuit_breaker).toBeDefined();
    expect(settings.retention_days).toBeDefined();

    // 2. Put settings
    const putSetRes = await servers.adminApp.inject({
      method: 'PUT',
      url: '/admin/settings',
      headers: adminMutatingHeaders(adminToken, csrfToken),
      payload: {
        retry: { attempts: 3, wait: 2 },
        circuit_breaker: { trip: 5, pause: 30 },
        retention_days: 14,
      },
    });
    expect(putSetRes.statusCode).toBe(200);

    // 3. Create app and rotate key
    await servers.adminApp.inject({
      method: 'POST',
      url: '/admin/applications',
      headers: adminMutatingHeaders(adminToken, csrfToken),
      payload: { id: 'test_app', name: 'Test Application' },
    });

    const rotateRes = await servers.adminApp.inject({
      method: 'POST',
      url: '/admin/applications/test_app/rotate-key',
      headers: adminMutatingHeaders(adminToken, csrfToken),
    });
    expect(rotateRes.statusCode).toBe(200);
    const rotated = JSON.parse(rotateRes.body);
    expect(rotated.api_key.startsWith('zy_')).toBe(true);
    expect(rotated.key_prefix).toBeDefined();
  });

  it('exposes provider catalog and phase-4 admin config routes without secret leaks', async () => {
    const catalogRes = await servers.adminApp.inject({
      method: 'GET',
      url: '/admin/catalog',
      headers: adminReadHeaders(adminToken),
    });
    expect(catalogRes.statusCode).toBe(200);
    const catalog = JSON.parse(catalogRes.body);
    expect(catalog.email?.some((e: { type: string }) => e.type === 'mailgun')).toBe(true);
    expect(catalog.sms?.some((e: { type: string }) => e.type === 'msg91')).toBe(true);

    const tplRes = await servers.adminApp.inject({
      method: 'POST',
      url: '/admin/templates',
      headers: adminMutatingHeaders(adminToken, csrfToken),
      payload: {
        id: 'tpl-test',
        name: 'Test SMS',
        channel: 'sms',
        body: 'Code {{code}}',
        dlt_id: '1107161234567890123',
        header: 'ACMEIN',
        category: 'otp',
      },
    });
    expect(tplRes.statusCode).toBe(201);

    const dltPut = await servers.adminApp.inject({
      method: 'PUT',
      url: '/admin/dlt',
      headers: adminMutatingHeaders(adminToken, csrfToken),
      payload: {
        dlt: {
          pe_id: '1100123456789012345',
          headers: [{ id: 'h1', header: 'ACMEIN', category: 'transactional' }],
        },
      },
    });
    expect(dltPut.statusCode).toBe(200);

    const secretProbe = 'SUPER_SECRET_PROBE_998877';
    await servers.adminApp.inject({
      method: 'PUT',
      url: '/admin/vault/catalog-probe',
      headers: adminMutatingHeaders(adminToken, csrfToken),
      payload: { secrets: { api_key: secretProbe } },
    });

    const aggregate = [
      catalogRes.body,
      tplRes.body,
      dltPut.body,
      (
        await servers.adminApp.inject({
          method: 'GET',
          url: '/admin/templates',
          headers: adminReadHeaders(adminToken),
        })
      ).body,
      (
        await servers.adminApp.inject({
          method: 'GET',
          url: '/admin/dlt',
          headers: adminReadHeaders(adminToken),
        })
      ).body,
      (
        await servers.adminApp.inject({
          method: 'GET',
          url: '/admin/insights',
          headers: adminReadHeaders(adminToken),
        })
      ).body,
      (
        await servers.adminApp.inject({
          method: 'GET',
          url: '/admin/logs/files',
          headers: adminReadHeaders(adminToken),
        })
      ).body,
    ].join('');
    expect(aggregate).not.toContain(secretProbe);
  });
});

