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 { resolveBaseConfig, resolveBaseSigningConfig, verifyRequestSignature, } from '../ProxyServerUtils.js';
import { RequestValidator } from '../RequestValidator.js';
const resolveRedisConfig = (overrides = {}) => {
    const host = overrides.redisHost ?? Env.get('REDIS_PROXY_TARGET_HOST', Env.get('REDIS_HOST', '127.0.0.1'));
    const port = overrides.redisPort ?? Env.getInt('REDIS_PROXY_TARGET_PORT', Env.getInt('REDIS_PORT', 6379));
    const password = overrides.redisPassword ??
        Env.get('REDIS_PROXY_TARGET_PASSWORD', Env.get('REDIS_PASSWORD', ''));
    const db = overrides.redisDb ?? Env.getInt('REDIS_PROXY_TARGET_DB', Env.getInt('REDIS_DB', 0));
    return { host, port, password, db };
};
const resolveConfig = (overrides = {}) => {
    const proxyConfig = resolveBaseConfig(overrides, 'REDIS');
    const redisConfig = resolveRedisConfig(overrides);
    const signingConfig = resolveBaseSigningConfig(overrides, 'REDIS');
    return {
        host: proxyConfig.host,
        port: proxyConfig.port,
        maxBodyBytes: proxyConfig.maxBodyBytes,
        redis: redisConfig,
        signing: {
            keyId: signingConfig.keyId,
            secret: signingConfig.secret,
            require: signingConfig.requireSigning,
            windowMs: signingConfig.signingWindowMs,
        },
    };
};
const validateCommandPayload = (payload) => {
    const command = payload['command'];
    const args = Array.isArray(payload['args']) ? payload['args'] : [];
    if (typeof command !== 'string' || command.trim() === '') {
        return { valid: false, error: { code: 'VALIDATION_ERROR', message: 'command is required' } };
    }
    return { valid: true, command, args };
};
const getRedisModule = async () => {
    const mod = await import('ioredis');
    return mod;
};
const createClient = async (config) => {
    const module = (await getRedisModule());
    const moduleDefault = module['default'];
    const candidates = [
        module['Redis'],
        module['default'],
        moduleDefault?.['Redis'],
        moduleDefault?.['default'],
        module,
    ];
    const RedisCtor = candidates.find((candidate) => typeof candidate === 'function');
    if (typeof RedisCtor !== 'function') {
        throw ErrorFactory.createConfigError("Redis proxy could not resolve a Redis constructor from 'ioredis'.");
    }
    const maxReconnectRetries = Math.max(0, Env.getInt('REDIS_PROXY_CONNECT_MAX_RETRIES', 3));
    const reconnectBaseMs = Math.max(50, Env.getInt('REDIS_PROXY_CONNECT_RETRY_BASE_MS', 200));
    const reconnectCapMs = Math.max(reconnectBaseMs, Env.getInt('REDIS_PROXY_CONNECT_RETRY_CAP_MS', 2000));
    const client = new RedisCtor({
        host: config.redis.host,
        port: config.redis.port,
        password: config.redis.password,
        db: config.redis.db,
        maxRetriesPerRequest: 1,
        enableOfflineQueue: false,
        lazyConnect: true,
        reconnectOnError: () => false,
        retryStrategy: (times) => {
            if (times > maxReconnectRetries) {
                return null;
            }
            return Math.min(times * reconnectBaseMs, reconnectCapMs);
        },
    });
    let lastErrorLogAt = 0;
    client.on('error', (error) => {
        const now = Date.now();
        if (now - lastErrorLogAt < 5000) {
            return;
        }
        lastErrorLogAt = now;
        Logger.warn('[RedisProxyServer] redis client error', error);
    });
    if (typeof client.connect === 'function') {
        await client.connect();
    }
    return client;
};
const executeCommand = async (client, command, args) => {
    const trimmed = command.trim();
    const lower = trimmed.toLowerCase();
    const candidate = client[lower];
    if (typeof candidate === 'function') {
        return candidate.apply(client, args);
    }
    if (typeof client.call === 'function') {
        return client.call(trimmed, ...args);
    }
    throw ErrorFactory.createValidationError(`Unsupported Redis command: ${trimmed}`);
};
const createBackend = (config) => ({
    name: 'redis',
    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/redis/command') {
            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 = validateCommandPayload(parsed.value);
        if (!validated.valid) {
            return {
                status: 400,
                body: {
                    code: validated.error?.code ?? 'VALIDATION_ERROR',
                    message: validated.error?.message ?? 'Invalid request',
                },
            };
        }
        const command = validated.command ?? '';
        if (command.trim() === '') {
            return {
                status: 400,
                body: { code: 'VALIDATION_ERROR', message: 'command is required' },
            };
        }
        try {
            const client = await createClient(config);
            try {
                const result = await executeCommand(client, command, validated.args ?? []);
                return { status: 200, body: { result } };
            }
            finally {
                await client.quit();
            }
        }
        catch (error) {
            return ErrorHandler.toProxyError(500, 'REDIS_PROXY_ERROR', String(error));
        }
    },
    health: async () => {
        try {
            const client = await createClient(config);
            const pingFn = client.ping;
            if (typeof pingFn === 'function') {
                await pingFn.apply(client);
            }
            else {
                await executeCommand(client, 'PING', []);
            }
            await client.quit();
            return { status: 200, body: { status: 'healthy' } };
        }
        catch (error) {
            Logger.warn('[RedisProxyServer] health check failed', error);
            return { status: 503, body: { status: 'unhealthy', error: String(error) } };
        }
    },
});
export const RedisProxyServer = Object.freeze({
    async start(overrides = {}) {
        const config = resolveConfig(overrides);
        const backend = createBackend(config);
        const server = createProxyServer({
            host: config.host,
            port: config.port,
            maxBodyBytes: config.maxBodyBytes,
            backend,
            verify: async (req, body) => {
                const verified = await verifyRequestSignature(req, body, config, 'RedisProxyServer');
                if (!verified.ok && verified.error) {
                    return { ok: false, status: verified.error.status, message: verified.error.message };
                }
                return { ok: true };
            },
        });
        await server.start();
        Logger.info(`[redis-proxy] Listening on http://${config.host}:${config.port}`);
    },
});
export default RedisProxyServer;
