import { Command } from 'commander';
import http from 'node:http';
import { createInterface } from 'node:readline';

export interface McpBridgeOptions {
  projectName: string;
  mcplocalUrl: string;
  token?: string | undefined;
  stdin: NodeJS.ReadableStream;
  stdout: NodeJS.WritableStream;
  stderr: NodeJS.WritableStream;
}

export function postJsonRpc(
  url: string,
  body: string,
  sessionId: string | undefined,
  token: string | undefined,
): Promise<{ status: number; headers: http.IncomingHttpHeaders; body: string }> {
  return new Promise((resolve, reject) => {
    const parsed = new URL(url);
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
      'Accept': 'application/json, text/event-stream',
    };
    if (sessionId) {
      headers['mcp-session-id'] = sessionId;
    }
    if (token) {
      headers['Authorization'] = `Bearer ${token}`;
    }

    const req = http.request(
      {
        hostname: parsed.hostname,
        port: parsed.port,
        path: parsed.pathname,
        method: 'POST',
        headers,
        timeout: 30_000,
      },
      (res) => {
        const chunks: Buffer[] = [];
        res.on('data', (chunk: Buffer) => chunks.push(chunk));
        res.on('end', () => {
          resolve({
            status: res.statusCode ?? 0,
            headers: res.headers,
            body: Buffer.concat(chunks).toString('utf-8'),
          });
        });
      },
    );
    req.on('error', reject);
    req.on('timeout', () => {
      req.destroy();
      reject(new Error('Request timed out'));
    });
    req.write(body);
    req.end();
  });
}

export function sendDelete(
  url: string,
  sessionId: string,
  token: string | undefined,
): Promise<void> {
  return new Promise((resolve) => {
    const parsed = new URL(url);
    const headers: Record<string, string> = {
      'mcp-session-id': sessionId,
    };
    if (token) {
      headers['Authorization'] = `Bearer ${token}`;
    }

    const req = http.request(
      {
        hostname: parsed.hostname,
        port: parsed.port,
        path: parsed.pathname,
        method: 'DELETE',
        headers,
        timeout: 5_000,
      },
      () => resolve(),
    );
    req.on('error', () => resolve()); // Best effort cleanup
    req.on('timeout', () => {
      req.destroy();
      resolve();
    });
    req.end();
  });
}

/**
 * Extract JSON-RPC messages from an HTTP response body.
 * Handles both plain JSON and SSE (text/event-stream) formats.
 */
export function extractJsonRpcMessages(contentType: string | undefined, body: string): string[] {
  if (contentType?.includes('text/event-stream')) {
    // Parse SSE: extract data: lines
    const messages: string[] = [];
    for (const line of body.split('\n')) {
      if (line.startsWith('data: ')) {
        messages.push(line.slice(6));
      }
    }
    return messages;
  }
  // Plain JSON response
  return [body];
}

/**
 * STDIO-to-Streamable-HTTP MCP bridge.
 *
 * Reads JSON-RPC messages line-by-line from stdin, POSTs them to
 * mcplocal's project endpoint, and writes responses to stdout.
 */
export async function runMcpBridge(opts: McpBridgeOptions): Promise<void> {
  const { projectName, mcplocalUrl, token, stdin, stdout, stderr } = opts;
  const endpointUrl = `${mcplocalUrl.replace(/\/$/, '')}/projects/${encodeURIComponent(projectName)}/mcp`;

  let sessionId: string | undefined;

  const rl = createInterface({ input: stdin, crlfDelay: Infinity });

  for await (const line of rl) {
    const trimmed = line.trim();
    if (!trimmed) continue;

    // Parse request ID for error responses
    let requestId: unknown = null;
    try {
      const parsed = JSON.parse(trimmed) as Record<string, unknown>;
      requestId = parsed.id ?? null;
    } catch {
      // Non-JSON or notification — no id to respond to
    }

    try {
      const result = await postJsonRpc(endpointUrl, trimmed, sessionId, token);

      // Capture session ID from first response
      if (!sessionId) {
        const sid = result.headers['mcp-session-id'];
        if (typeof sid === 'string') {
          sessionId = sid;
        }
      }

      if (result.status >= 400) {
        stderr.write(`MCP bridge error: HTTP ${result.status}: ${result.body}\n`);
      }

      // Handle both plain JSON and SSE responses
      const messages = extractJsonRpcMessages(result.headers['content-type'], result.body);
      for (const msg of messages) {
        const trimmedMsg = msg.trim();
        if (trimmedMsg) {
          stdout.write(trimmedMsg + '\n');
        }
      }
    } catch (err) {
      const errMsg = err instanceof Error ? err.message : String(err);
      stderr.write(`MCP bridge error: ${errMsg}\n`);

      // Send JSON-RPC error response so the client doesn't hang
      if (requestId !== null) {
        const errorResponse = JSON.stringify({
          jsonrpc: '2.0',
          id: requestId,
          error: { code: -32603, message: `Bridge error: ${errMsg}` },
        });
        stdout.write(errorResponse + '\n');
      }
    }
  }

  // stdin closed — cleanup session
  if (sessionId) {
    await sendDelete(endpointUrl, sessionId, token);
  }
}

export interface McpCommandDeps {
  getProject: () => string | undefined;
  configLoader?: () => { mcplocalUrl: string };
  credentialsLoader?: () => { token: string } | null;
}

export function createMcpCommand(deps: McpCommandDeps): Command {
  const cmd = new Command('mcp')
    .description('MCP STDIO transport bridge — connects stdin/stdout to a project MCP endpoint')
    .passThroughOptions()
    .option('-p, --project <name>', 'Project name')
    .action(async (opts: { project?: string }) => {
      // Accept -p/--project on the command itself, or fall back to global --project
      const projectName = opts.project ?? deps.getProject();
      if (!projectName) {
        process.stderr.write('Error: --project is required for the mcp command\n');
        process.exitCode = 1;
        return;
      }

      let mcplocalUrl = 'http://localhost:3200';
      if (deps.configLoader) {
        mcplocalUrl = deps.configLoader().mcplocalUrl;
      } else {
        try {
          const { loadConfig } = await import('../config/index.js');
          mcplocalUrl = loadConfig().mcplocalUrl;
        } catch {
          // Use default
        }
      }

      let token: string | undefined;
      if (deps.credentialsLoader) {
        token = deps.credentialsLoader()?.token;
      } else {
        try {
          const { loadCredentials } = await import('../auth/index.js');
          token = loadCredentials()?.token;
        } catch {
          // No credentials
        }
      }

      await runMcpBridge({
        projectName,
        mcplocalUrl,
        token,
        stdin: process.stdin,
        stdout: process.stdout,
        stderr: process.stderr,
      });
    });

  return cmd;
}
