import { createServer } from 'http';
import { EventEmitter } from 'events';
export class CopilotProxy extends EventEmitter {
    server = null;
    config;
    requestCount = 0;
    /** Rate limiter: IP → { count, windowStart } */
    rateLimitMap = new Map();
    rateLimitCleanupTimer = null;
    constructor(config) {
        super();
        this.config = {
            requireAuth: false,
            maxTokensLimit: 8192,
            rateLimitPerMinute: 60,
            ...config,
        };
        // Cleanup stale rate limit entries every 5 minutes
        this.rateLimitCleanupTimer = setInterval(() => {
            const now = Date.now();
            for (const [ip, entry] of this.rateLimitMap) {
                if (now - entry.windowStart > 120000) {
                    this.rateLimitMap.delete(ip);
                }
            }
        }, 300000);
    }
    async start() {
        return new Promise((resolve, reject) => {
            this.server = createServer((req, res) => {
                this.handleRequest(req, res).catch((err) => {
                    this.emit('error', err);
                    if (!res.writableEnded) {
                        res.writeHead(500, { 'Content-Type': 'application/json' });
                        res.end(JSON.stringify({
                            error: { message: 'Internal server error', type: 'server_error', code: 500 }
                        }));
                    }
                });
            });
            this.server.on('error', reject);
            this.server.listen(this.config.port, this.config.host, () => {
                this.emit('listening');
                resolve();
            });
        });
    }
    async handleRequest(req, res) {
        this.requestCount++;
        // Rate limiting
        if (this.config.rateLimitPerMinute && this.config.rateLimitPerMinute > 0) {
            const clientIp = req.socket.remoteAddress || 'unknown';
            if (!this.checkRateLimit(clientIp)) {
                res.writeHead(429, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({
                    error: { message: 'Rate limit exceeded', type: 'rate_limit_error', code: 429 }
                }));
                return;
            }
        }
        if (!this.authenticate(req)) {
            res.writeHead(401, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({
                error: { message: 'Unauthorized', type: 'auth_error', code: 401 }
            }));
            return;
        }
        const url = req.url ?? '/';
        const method = req.method ?? 'GET';
        if (method === 'GET' && url === '/health') {
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ status: 'ok' }));
            return;
        }
        if (method === 'GET' && url === '/v1/models') {
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({
                data: [{ id: 'codebuddy', object: 'model' }]
            }));
            return;
        }
        if (method === 'POST' && (url === '/v1/completions' || url === '/v1/engines/codex/completions')) {
            try {
                const body = await this.parseBody(req);
                const parsed = JSON.parse(body);
                if (!parsed.prompt) {
                    res.writeHead(400, { 'Content-Type': 'application/json' });
                    res.end(JSON.stringify({
                        error: { message: 'Missing required field: prompt', type: 'invalid_request_error', code: 400 }
                    }));
                    return;
                }
                if (parsed.max_tokens === undefined) {
                    parsed.max_tokens = this.config.maxTokens;
                }
                // Clamp max_tokens to prevent abuse
                const limit = this.config.maxTokensLimit || 8192;
                if (parsed.max_tokens > limit) {
                    parsed.max_tokens = limit;
                }
                const response = await this.config.onCompletion(parsed);
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify(response));
            }
            catch (err) {
                // Don't leak internal error details to clients
                const isSyntaxError = err instanceof SyntaxError;
                const message = isSyntaxError
                    ? 'Invalid JSON in request body'
                    : 'Internal completion error';
                const type = isSyntaxError ? 'invalid_request_error' : 'server_error';
                const code = isSyntaxError ? 400 : 500;
                res.writeHead(code, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ error: { message, type, code } }));
            }
            return;
        }
        res.writeHead(404, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({
            error: { message: 'Not found', type: 'not_found', code: 404 }
        }));
    }
    parseBody(req) {
        const MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB
        return new Promise((resolve, reject) => {
            const chunks = [];
            let totalSize = 0;
            req.on('data', (chunk) => {
                totalSize += chunk.length;
                if (totalSize > MAX_BODY_SIZE) {
                    req.destroy();
                    reject(new Error('Payload too large'));
                    return;
                }
                chunks.push(chunk);
            });
            req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
            req.on('error', reject);
        });
    }
    authenticate(req) {
        // If requireAuth is true but no token is configured, reject all
        if (!this.config.authToken && this.config.requireAuth) {
            return false;
        }
        // If no auth token configured and requireAuth is false, allow (development mode)
        if (!this.config.authToken) {
            return true;
        }
        const header = req.headers.authorization;
        if (!header) {
            return false;
        }
        return header === `Bearer ${this.config.authToken}`;
    }
    /**
     * Token-bucket rate limiter per IP.
     */
    checkRateLimit(clientIp) {
        const limit = this.config.rateLimitPerMinute || 60;
        const now = Date.now();
        const windowMs = 60000; // 1 minute
        let entry = this.rateLimitMap.get(clientIp);
        if (!entry || (now - entry.windowStart) > windowMs) {
            entry = { count: 0, windowStart: now };
            this.rateLimitMap.set(clientIp, entry);
        }
        entry.count++;
        return entry.count <= limit;
    }
    getRequestCount() {
        return this.requestCount;
    }
    isRunning() {
        return this.server !== null && this.server.listening;
    }
    async stop() {
        if (this.rateLimitCleanupTimer) {
            clearInterval(this.rateLimitCleanupTimer);
            this.rateLimitCleanupTimer = null;
        }
        return new Promise((resolve, reject) => {
            if (!this.server) {
                resolve();
                return;
            }
            this.server.close((err) => {
                this.server = null;
                if (err) {
                    reject(err);
                }
                else {
                    resolve();
                }
            });
        });
    }
}
//# sourceMappingURL=copilot-proxy.js.map