import { Env } from '../../config/env.js';
import { Logger } from '../../config/logger.js';
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
import { ErrorHandler } from '../ErrorHandler.js';
import { createProxyServer } from '../ProxyServer.js';
import { resolveProxySigningConfig } from '../ProxySigningConfigResolver.js';
import { verifyProxySignatureIfNeeded } from '../ProxySigningRequest.js';
import { RequestValidator } from '../RequestValidator.js';
const resolveProxyConfig = (overrides = {}) => {
    const host = overrides?.host ?? Env.get('MONGODB_PROXY_HOST', Env.HOST ?? '127.0.0.1');
    const port = overrides.port ?? Env.getInt('MONGODB_PROXY_PORT', Env.PORT ?? 3000);
    const maxBodyBytes = overrides.maxBodyBytes ?? Env.getInt('MONGODB_PROXY_MAX_BODY_BYTES', Env.MAX_BODY_SIZE ?? 0);
    return { host, port, maxBodyBytes };
};
const resolveMongoConfig = (overrides = {}) => {
    const uri = overrides.mongoUri ?? Env.get('MONGO_URI', 'mongodb://localhost:27017');
    const database = overrides.mongoDb ?? Env.get('MONGO_DB', 'zintrust');
    return { uri, database };
};
const resolveSigningConfig = (overrides = {}) => resolveProxySigningConfig(overrides, {
    keyIdEnvVar: 'MONGODB_PROXY_KEY_ID',
    secretEnvVar: 'MONGODB_PROXY_SECRET',
    requireEnvVar: 'MONGODB_PROXY_REQUIRE_SIGNING',
    windowEnvVar: 'MONGODB_PROXY_SIGNING_WINDOW_MS',
});
const resolveConfig = (overrides = {}) => {
    const proxyConfig = resolveProxyConfig(overrides);
    const mongoConfig = resolveMongoConfig(overrides);
    const signingConfig = resolveSigningConfig(overrides);
    return {
        host: proxyConfig.host,
        port: proxyConfig.port,
        maxBodyBytes: proxyConfig.maxBodyBytes,
        mongoOptions: mongoConfig,
        signing: {
            keyId: signingConfig.keyId,
            secret: signingConfig.secret,
            require: signingConfig.requireSigning,
            windowMs: signingConfig.signingWindowMs,
        },
    };
};
const validateOperationPayload = (payload) => {
    const operation = payload['operation'];
    const collection = payload['collection'];
    const args = payload['args'];
    if (typeof operation !== 'string' || operation.trim() === '') {
        return {
            valid: false,
            error: { code: 'VALIDATION_ERROR', message: 'operation is required' },
        };
    }
    if (typeof collection !== 'string' || collection.trim() === '') {
        return {
            valid: false,
            error: { code: 'VALIDATION_ERROR', message: 'collection is required' },
        };
    }
    return {
        valid: true,
        operation,
        collection,
        args: args ?? {},
    };
};
const getMongoModule = async () => {
    const mongoModule = (await import('mongodb'));
    const MongoDBClient = mongoModule.MongoClient;
    if (typeof MongoDBClient !== 'function') {
        throw ErrorFactory.createDatabaseError('MongoDB driver is unavailable');
    }
    return { MongoClient: MongoDBClient };
};
const createMongoClient = async (uri) => {
    const { MongoClient: MongoDBClient } = await getMongoModule();
    const client = new MongoDBClient(uri);
    await client.connect();
    return client;
};
const callMethod = async (coll, methodName, ...params) => {
    const method = coll[methodName];
    if (typeof method === 'function') {
        const result = method(...params);
        if (result !== null && typeof result === 'object' && 'toArray' in result) {
            const toArray = result.toArray;
            if (typeof toArray === 'function') {
                return toArray();
            }
        }
        return result;
    }
    throw ErrorFactory.createDatabaseError(`Method ${methodName} not found on collection`);
};
const OPERATION_HANDLERS = Object.freeze({
    find: async (coll, args) => callMethod(coll, 'find', args['filter'] ?? {}),
    findOne: async (coll, args) => callMethod(coll, 'findOne', args['filter'] ?? {}),
    insertOne: async (coll, args) => callMethod(coll, 'insertOne', args['document'] ?? {}),
    insertMany: async (coll, args) => callMethod(coll, 'insertMany', args['documents'] ?? []),
    updateOne: async (coll, args) => callMethod(coll, 'updateOne', args['filter'] ?? {}, args['update'] ?? {}),
    updateMany: async (coll, args) => callMethod(coll, 'updateMany', args['filter'] ?? {}, args['update'] ?? {}),
    deleteOne: async (coll, args) => callMethod(coll, 'deleteOne', args['filter'] ?? {}),
    deleteMany: async (coll, args) => callMethod(coll, 'deleteMany', args['filter'] ?? {}),
    countDocuments: async (coll, args) => callMethod(coll, 'countDocuments', args['filter'] ?? {}),
    aggregate: async (coll, args) => callMethod(coll, 'aggregate', args['pipeline'] ?? []),
});
const executeOperation = async (client, dbName, collectionName, operation, args) => {
    const db = client.db(dbName);
    const coll = db.collection(collectionName);
    if (Object.prototype.hasOwnProperty.call(OPERATION_HANDLERS, operation)) {
        const handler = OPERATION_HANDLERS[operation];
        return handler(coll, args);
    }
    throw ErrorFactory.createDatabaseError(`Unsupported MongoDB operation: ${String(operation)}`);
};
const createBackend = (client, config) => ({
    name: 'mongodb',
    handle: async (request) => {
        const methodError = RequestValidator.requirePost(request.method);
        if (methodError) {
            return {
                status: 405,
                body: { code: methodError.code, message: methodError.message },
            };
        }
        if (request.path !== '/zin/mongodb/operation') {
            return { status: 404, body: { code: 'NOT_FOUND', message: 'Unknown endpoint' } };
        }
        const parsed = RequestValidator.parseJson(request.body);
        if (!parsed.ok) {
            return { status: 400, body: { code: parsed.error.code, message: parsed.error.message } };
        }
        const validated = validateOperationPayload(parsed.value);
        if (!validated.valid) {
            return {
                status: 400,
                body: {
                    code: validated.error?.code ?? 'VALIDATION_ERROR',
                    message: validated.error?.message ?? 'Invalid request',
                },
            };
        }
        try {
            const result = await executeOperation(client, config.mongoOptions.database, validated.collection ?? '', validated.operation ?? '', validated.args ?? {});
            return { status: 200, body: { success: true, result } };
        }
        catch (error) {
            Logger.error('MongoDB proxy operation failed', { error });
            return ErrorHandler.toProxyError(500, 'MONGODB_PROXY_ERROR', String(error));
        }
    },
    health: async () => {
        try {
            client.db(config.mongoOptions.database);
            await Promise.resolve();
            return { status: 200, body: { status: 'ok' } };
        }
        catch (error) {
            await Promise.resolve();
            return { status: 503, body: { status: 'unhealthy', error: String(error) } };
        }
    },
    shutdown: async () => {
        await client.close();
    },
});
const createVerifier = (config) => {
    return async (req, body) => {
        const verified = await verifyProxySignatureIfNeeded(req, body, config.signing);
        if (!verified.ok) {
            const error = verified.error ?? { status: 401, message: 'Unauthorized' };
            return { ok: false, status: error.status, message: error.message };
        }
        return { ok: true };
    };
};
export const MongoDBProxyServer = Object.freeze({
    async start(overrides = {}) {
        const config = resolveConfig(overrides);
        Logger.info(`Starting MongoDB proxy on ${config.host}:${config.port} → ${config.mongoOptions.uri}`);
        const client = await createMongoClient(config.mongoOptions.uri);
        const backend = createBackend(client, config);
        const verifier = createVerifier(config);
        const server = createProxyServer({
            host: config.host,
            port: config.port,
            maxBodyBytes: config.maxBodyBytes,
            backend,
            verify: verifier,
        });
        await server.start();
        Logger.info(`✓ MongoDB proxy listening on ${config.host}:${config.port}`);
        return {
            server,
            client,
            async close() {
                await server.stop();
                await client.close();
                Logger.info('MongoDB proxy server closed');
            },
        };
    },
});
