/* eslint-disable @typescript-eslint/require-await */
/**
 * Microservices Architecture for ZinTrust Framework
 * Sealed namespace pattern with immutable microservice management
 */
import { Env } from '../config/env.js';
import { Logger } from '../config/logger.js';
import { validateUrl } from '../security/UrlValidator.js';
import { ErrorFactory } from '../exceptions/ZintrustError.js';
import { getServiceId, serviceMatchesAllowList } from './ServiceManifest.js';
const services = new Map();
let instance;
let basePort = 3000;
let nextPortOffset = 0;
function normalizeEnabledServices() {
    return getEnabledServices();
}
function isServiceEnabledByEnv(serviceId, serviceName) {
    const enabled = normalizeEnabledServices();
    return serviceMatchesAllowList(serviceId, serviceName, enabled);
}
function resolveServiceKey(lookup) {
    if (services.has(lookup)) {
        return lookup;
    }
    const matches = Array.from(services.values()).filter((service) => service.name === lookup);
    if (matches.length === 0) {
        return undefined;
    }
    if (matches.length > 1) {
        throw ErrorFactory.createValidationError(`Ambiguous service lookup '${lookup}'. Use domain/name instead.`);
    }
    return matches[0]?.id;
}
function toCallOptions(pathOrOptions, options) {
    if (typeof pathOrOptions === 'string') {
        return {
            method: 'GET',
            path: pathOrOptions,
            headers: options?.['headers'] ?? undefined,
            body: options?.['body'],
            timeout: options?.['timeout'] ?? undefined,
        };
    }
    return {
        method: pathOrOptions['method'] ?? 'GET',
        path: pathOrOptions['path'],
        headers: pathOrOptions['headers'] ?? undefined,
        body: pathOrOptions['body'],
        timeout: pathOrOptions['timeout'] ?? undefined,
    };
}
const create = () => {
    return getMicroserviceManager().create();
};
const getInstance = () => {
    instance ??= getMicroserviceManager();
    return instance;
};
const reset = () => {
    services.clear();
    instance = undefined;
    basePort = 3000;
    nextPortOffset = 0;
};
const initialize = (configs = [], initBasePort = 3000) => {
    instance ??= getMicroserviceManager();
    basePort = initBasePort;
    nextPortOffset = 0;
    for (const config of configs) {
        registerService(config);
    }
    return getMicroserviceManager();
};
const register = (config) => {
    return registerService(config);
};
const registerService = (config) => {
    const serviceId = getServiceId(config.domain, config.name);
    if (isServiceEnabledByEnv(serviceId, config.name) === false) {
        Logger.info(`Service ${serviceId} not in SERVICES env; skipping registration`);
        return null;
    }
    const assignedPort = config.port ?? basePort + nextPortOffset;
    nextPortOffset += 1;
    const healthCheckUrl = typeof config.healthCheck === 'string'
        ? config.healthCheck
        : config.healthCheckUrl ?? '/health';
    const serviceConfig = {
        ...config,
        id: serviceId,
        port: assignedPort,
        baseUrl: config.baseUrl ?? `http://localhost:${assignedPort}`,
        healthCheckUrl,
        status: config.status ?? 'starting',
    };
    services.set(serviceId, serviceConfig);
    Logger.info(`Registered microservice: ${serviceId}`);
    return serviceConfig;
};
const startService = async (name, _handler) => {
    const serviceKey = resolveServiceKey(name);
    const service = serviceKey === undefined ? undefined : services.get(serviceKey);
    if (service === undefined) {
        throw ErrorFactory.createNotFoundError('Service not found', { name });
    }
    service.status = 'running';
    Logger.info(`Service started: ${service.id ?? name}`);
    return true;
};
const stopService = async (name) => {
    const serviceKey = resolveServiceKey(name);
    const service = serviceKey === undefined ? undefined : services.get(serviceKey);
    if (service === undefined) {
        return false;
    }
    service.status = 'stopped';
    Logger.info(`Service stopped: ${service.id ?? name}`);
    return true;
};
const stopAllServices = async () => {
    Logger.info('Stopping all microservices...');
    for (const service of services.values()) {
        service.status = 'stopped';
    }
};
const getService = (domain, name) => {
    return services.get(getServiceId(domain, name));
};
const getAllServices = () => {
    return Array.from(services.values());
};
const getServicesByDomain = (domain) => {
    return Array.from(services.values()).filter((s) => s.domain === domain);
};
const getResolvedService = (name) => {
    const serviceKey = resolveServiceKey(name);
    return serviceKey === undefined ? undefined : services.get(serviceKey);
};
const getRequiredService = (name) => {
    const service = getResolvedService(name);
    if (service === undefined) {
        throw ErrorFactory.createNotFoundError('Service not found', { name });
    }
    return service;
};
const assertServiceRunning = (name, service) => {
    if (service.status === 'running')
        return;
    throw ErrorFactory.createConnectionError('Service not running', {
        name,
        status: service.status,
    });
};
const resolveServiceBaseUrl = (service) => {
    return service.baseUrl ?? `http://localhost:${service.port ?? basePort}`;
};
const createAbortTimeout = (timeoutMs, controller) => {
    if (typeof timeoutMs !== 'number') {
        return undefined;
    }
    return globalThis.setTimeout(() => controller.abort(), timeoutMs);
};
const buildRequestInit = (method, callOptions, signal) => {
    const init = {
        method,
        headers: callOptions.headers,
        signal,
    };
    if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
        init.body = JSON.stringify(callOptions.body ?? {});
    }
    return init;
};
const normalizeServiceResponse = async (response) => {
    const data = await response.json().catch(() => ({}));
    return {
        statusCode: response.status,
        data,
    };
};
const callService = async (name, pathOrOptions, options) => {
    const service = getRequiredService(name);
    assertServiceRunning(name, service);
    const callOptions = toCallOptions(pathOrOptions, options);
    const pathValue = callOptions.path ?? '/';
    const method = (callOptions.method ?? 'GET').toUpperCase();
    const url = `${resolveServiceBaseUrl(service)}${pathValue}`;
    validateUrl(url);
    const controller = new AbortController();
    const timeoutId = createAbortTimeout(callOptions.timeout, controller);
    try {
        const response = await globalThis.fetch(url, buildRequestInit(method, callOptions, controller.signal));
        return await normalizeServiceResponse(response);
    }
    catch (error) {
        Logger.error('Failed to call service', error);
        throw ErrorFactory.createTryCatchError('Failed to call service', error);
    }
    finally {
        if (timeoutId !== undefined) {
            clearTimeout(timeoutId);
        }
    }
};
const checkServiceHealth = async (name) => {
    const service = getResolvedService(name);
    if (service === undefined) {
        return false;
    }
    const healthPath = service.healthCheckUrl ?? '/health';
    const url = `${resolveServiceBaseUrl(service)}${healthPath}`;
    validateUrl(url);
    try {
        const response = await globalThis.fetch(url, { method: 'GET' });
        const healthy = response.ok === true;
        service.lastHealthCheck = Date.now();
        return healthy;
    }
    catch (error) {
        service.lastHealthCheck = Date.now();
        const message = error instanceof Error ? error.message : String(error);
        Logger.error('Health check failed', message);
        return false;
    }
};
const healthCheckAll = async () => {
    const names = Array.from(services.keys());
    const entries = await Promise.all(names.map(async (name) => [name, await checkServiceHealth(name)]));
    return Object.fromEntries(entries);
};
const getStatusSummary = () => {
    const allServices = Array.from(services.values());
    const runningServices = allServices.filter((s) => s.status === 'running').length;
    return {
        totalServices: allServices.length,
        runningServices,
        services: allServices.map((s) => ({
            id: s.id,
            name: s.name,
            domain: s.domain,
            version: s.version,
            status: s.status,
            lastHealthCheck: s.lastHealthCheck,
        })),
        timestamp: Date.now(),
    };
};
const discoverServices = async () => {
    return Array.from(services.values());
};
const getMicroserviceManager = () => MicroserviceManager;
export const MicroserviceManager = Object.freeze({
    create,
    getInstance,
    reset,
    initialize,
    register,
    registerService,
    startService,
    stopService,
    stopAllServices,
    getService,
    getAllServices,
    getServicesByDomain,
    callService,
    checkServiceHealth,
    healthCheckAll,
    getStatusSummary,
    discoverServices,
});
export function isMicroservicesEnabled() {
    const direct = (Env.get('MICROSERVICES') ?? '').trim();
    if (direct.toLowerCase() === 'true') {
        return true;
    }
    return Env.getBool('ENABLE_MICROSERVICES', false);
}
export function getEnabledServices() {
    const raw = (Env.get('SERVICES') ?? '').trim();
    if (raw.length === 0) {
        return [];
    }
    return raw
        .split(',')
        .map((part) => part.trim())
        .filter((part) => part.length > 0);
}
// Re-export functions for backward compatibility
export { callService, checkServiceHealth, create, discoverServices, getAllServices, getInstance, getService, getServicesByDomain, getStatusSummary, healthCheckAll, initialize, register, registerService, reset, startService, stopAllServices, stopService, };
export default MicroserviceManager;
