import { toErrorMessage } from '../../utils/error.js';
import { getRootLogger } from '../../utils/logger.js';
import { ConnectorManager } from './lifecycle.js';
export class TenantConnectorManager {
    tenants = new Map();
    registry;
    credentialVault;
    defaultRateLimit;
    listeners = [];
    log;
    constructor(config) {
        this.registry = config.registry;
        this.credentialVault = config.credentialVault;
        this.defaultRateLimit = config.defaultRateLimit;
        this.log = getRootLogger().child({ component: 'TenantConnectorManager' });
    }
    registerTenant(descriptor, rateLimit) {
        if (this.tenants.has(descriptor.id)) {
            this.log.warn(`Tenant "${descriptor.id}" already registered, skipping.`);
            return;
        }
        const manager = new ConnectorManager({ registry: this.registry });
        manager.on((event) => {
            this.emitTenantEvent(descriptor.id, event);
        });
        this.tenants.set(descriptor.id, {
            descriptor,
            manager,
            rateLimit: rateLimit ?? this.defaultRateLimit,
            rateWindows: new Map(),
        });
        this.log.info(`Tenant registered: ${descriptor.id} (${descriptor.name})`);
    }
    async unregisterTenant(tenantId) {
        const state = this.tenants.get(tenantId);
        if (!state)
            return;
        await state.manager.disconnectAll();
        this.tenants.delete(tenantId);
        this.log.info(`Tenant unregistered: ${tenantId}`);
    }
    getTenant(tenantId) {
        return this.tenants.get(tenantId)?.descriptor;
    }
    listTenants() {
        return Array.from(this.tenants.values()).map((s) => s.descriptor);
    }
    setTenantRateLimit(tenantId, rateLimit) {
        const state = this.getTenantOrThrow(tenantId);
        state.rateLimit = rateLimit;
    }
    checkRateLimit(state, instanceId) {
        const limit = state.rateLimit;
        if (!limit)
            return true;
        const now = Date.now();
        let window = state.rateWindows.get(instanceId);
        if (!window) {
            window = { timestamps: [] };
            state.rateWindows.set(instanceId, window);
        }
        window.timestamps = window.timestamps.filter((t) => now - t < limit.windowMs);
        if (window.timestamps.length >= limit.maxRequests) {
            return false;
        }
        window.timestamps.push(now);
        return true;
    }
    async createInstance(tenantId, config, connector) {
        const state = this.getTenantOrThrow(tenantId);
        return state.manager.createInstance(config, connector);
    }
    async connect(tenantId, instanceId) {
        const state = this.getTenantOrThrow(tenantId);
        if (this.credentialVault) {
            const instance = state.manager.getInstance(instanceId);
            if (instance && !instance.config.auth) {
                const creds = await this.credentialVault.list(tenantId, instance.connectorId);
                const credId = creds[0]?.id;
                if (credId) {
                    const auth = await this.credentialVault.retrieve(credId);
                    if (auth) {
                        instance.config.auth = auth;
                        this.log.info(`Auto-resolved credential "${credId}" for instance "${instanceId}" (tenant: ${tenantId})`);
                    }
                }
            }
        }
        return state.manager.connect(instanceId);
    }
    async connectWithCredential(tenantId, instanceId, credentialId) {
        if (!this.credentialVault) {
            throw new Error('No credential vault configured on TenantConnectorManager');
        }
        const state = this.getTenantOrThrow(tenantId);
        const instance = state.manager.getInstance(instanceId);
        if (!instance) {
            throw new Error(`Connector instance not found: "${instanceId}"`);
        }
        const auth = await this.credentialVault.retrieve(credentialId);
        if (!auth) {
            throw new Error(`Credential not found: "${credentialId}"`);
        }
        instance.config.auth = auth;
        this.log.info(`Credential "${credentialId}" applied to instance "${instanceId}" (tenant: ${tenantId})`);
        return state.manager.connect(instanceId);
    }
    async storeCredential(tenantId, connectorId, label, auth) {
        if (!this.credentialVault) {
            throw new Error('No credential vault configured on TenantConnectorManager');
        }
        this.getTenantOrThrow(tenantId);
        return this.credentialVault.store(tenantId, connectorId, label, auth);
    }
    async listCredentials(tenantId, connectorId) {
        if (!this.credentialVault) {
            return [];
        }
        this.getTenantOrThrow(tenantId);
        return this.credentialVault.list(tenantId, connectorId);
    }
    async revokeCredential(credentialId) {
        if (!this.credentialVault) {
            return false;
        }
        return this.credentialVault.revoke(credentialId);
    }
    async disconnect(tenantId, instanceId) {
        const state = this.getTenantOrThrow(tenantId);
        return state.manager.disconnect(instanceId);
    }
    async execute(tenantId, params) {
        const state = this.getTenantOrThrow(tenantId);
        if (!this.checkRateLimit(state, params.instanceId)) {
            return {
                success: false,
                output: null,
                error: `Rate limit exceeded for tenant "${tenantId}" on instance "${params.instanceId}"`,
                durationMs: 0,
            };
        }
        return state.manager.execute(params);
    }
    async healthCheck(tenantId, instanceId) {
        const state = this.getTenantOrThrow(tenantId);
        return state.manager.healthCheck(instanceId);
    }
    async removeInstance(tenantId, instanceId) {
        const state = this.getTenantOrThrow(tenantId);
        return state.manager.removeInstance(instanceId);
    }
    listInstances(tenantId) {
        const state = this.getTenantOrThrow(tenantId);
        return state.manager.listInstances();
    }
    listConnectedInstances(tenantId) {
        const state = this.getTenantOrThrow(tenantId);
        return state.manager.listConnectedInstances();
    }
    getManagerForTenant(tenantId) {
        return this.getTenantOrThrow(tenantId).manager;
    }
    getCredentialVault() {
        return this.credentialVault;
    }
    async disconnectAll() {
        const promises = [];
        for (const state of this.tenants.values()) {
            promises.push(state.manager.disconnectAll());
        }
        await Promise.allSettled(promises);
    }
    on(listener) {
        this.listeners.push(listener);
    }
    off(listener) {
        const index = this.listeners.indexOf(listener);
        if (index >= 0)
            this.listeners.splice(index, 1);
    }
    emitTenantEvent(tenantId, event) {
        for (const listener of this.listeners) {
            try {
                listener(event);
            }
            catch (err) {
                this.log.error('Tenant event listener error', {
                    tenantId,
                    error: toErrorMessage(err),
                });
            }
        }
    }
    getTenantOrThrow(tenantId) {
        const state = this.tenants.get(tenantId);
        if (!state) {
            throw new Error(`Tenant not found: "${tenantId}". Register it first via registerTenant().`);
        }
        return state;
    }
}
//# sourceMappingURL=tenant.js.map