import { context, SpanKind, SpanStatusCode, trace } from '@opentelemetry/api';
import { KeyValueStorageService, StreamStorageService, VectorStorageService, QueueStorageService, EmailStorageService, ScheduleService, TaskStorageService, StructuredError, } from '@agentuity/core';
import { APIClient, createServerFetchAdapter, getServiceUrls } from '@agentuity/server';
import { HTTPSandboxService } from './services/sandbox';
import { CompositeSessionEventProvider, LocalSessionEventProvider, JSONSessionEventProvider, HTTPSessionEventProvider, } from './services/session';
import { CompositeEvalRunEventProvider, LocalEvalRunEventProvider, JSONEvalRunEventProvider, HTTPEvalRunEventProvider, } from './services/evalrun';
import { LocalThreadProvider } from './services/thread/local';
import { injectTraceContextToHeaders } from './otel/http';
import { getTracer } from './_server';
import { populateAgentsRegistry } from './agent.js';
import { getSDKVersion, isAuthenticated, isProduction } from './_config';
import { localServicesLogged } from './_globals';
import { DefaultSessionProvider, DefaultThreadProvider, } from './session';
import { LocalKeyValueStorage, LocalStreamStorage, LocalVectorStorage, LocalQueueStorage, LocalEmailStorage, LocalTaskStorage, getLocalDB, normalizeProjectPath, createLocalStorageRouter, } from './services/local';
const userAgent = `Agentuity SDK/${getSDKVersion()}`;
// Lazy getters - these must be functions to read env vars AFTER bootstrapRuntimeEnv() runs
const getSdkKey = () => process.env.AGENTUITY_SDK_KEY;
const getBearerKey = () => `Bearer ${getSdkKey()}`;
const getRegion = () => process.env.AGENTUITY_REGION ?? 'usc';
const getLazyServiceUrls = () => getServiceUrls(getRegion());
const getKvBaseUrl = () => getLazyServiceUrls().keyvalue;
const getStreamBaseUrl = () => getLazyServiceUrls().stream;
const getVectorBaseUrl = () => getLazyServiceUrls().vector;
const getCatalystBaseUrl = () => getLazyServiceUrls().catalyst;
const getQueueBaseUrl = () => getCatalystBaseUrl();
const getEmailBaseUrl = () => getLazyServiceUrls().email;
const getTaskBaseUrl = () => getCatalystBaseUrl();
let adapter;
const createFetchAdapter = (logger) => createServerFetchAdapter({
    headers: {
        Authorization: getBearerKey(),
        'User-Agent': userAgent,
    },
    onBefore: async (url, options, callback) => {
        logger.debug('before request: %s with options: %s', url, options);
        if (!options.telemetry) {
            return callback();
        }
        options.headers = { ...options.headers, ...injectTraceContextToHeaders() };
        const tracer = getTracer() ?? trace.getTracer('agentuity');
        const currentContext = context.active();
        const span = tracer.startSpan(options.telemetry.name, { attributes: options.telemetry.attributes, kind: SpanKind.CLIENT }, currentContext);
        const spanContext = trace.setSpan(currentContext, span);
        try {
            await context.with(spanContext, callback);
            span.setStatus({ code: SpanStatusCode.OK });
        }
        catch (err) {
            const e = err;
            span.recordException(e);
            span.setStatus({ code: SpanStatusCode.ERROR, message: e?.message ?? String(err) });
            throw err;
        }
        finally {
            span.end();
        }
    },
    onAfter: async (url, options, result, err) => {
        logger.debug('after request: %s (%d) => %s', url, result.response.status, err);
        if (err) {
            return;
        }
        const span = trace.getSpan(context.active());
        switch (options.telemetry?.name) {
            case 'agentuity.keyvalue.get': {
                if (result.response.status === 404) {
                    span?.addEvent('miss');
                }
                else if (result.response.ok) {
                    span?.addEvent('hit');
                }
                break;
            }
            case 'agentuity.stream.create': {
                if (result.response.ok) {
                    const res = result.data;
                    span?.setAttributes({
                        'stream.id': res.id,
                        'stream.url': `${getStreamBaseUrl()}/${res.id}`,
                    });
                }
                break;
            }
            case 'agentuity.stream.list': {
                const response = result.data;
                if (response && span) {
                    span.setAttributes({
                        'stream.count': response.streams.length,
                        'stream.total': response.total,
                    });
                }
                break;
            }
            case 'agentuity.vector.upsert': {
                if (result.response.ok) {
                    const data = result.data;
                    span?.setAttributes({
                        'vector.count': data.length,
                    });
                }
                break;
            }
            case 'agentuity.vector.search': {
                if (result.response.ok) {
                    const data = result.data;
                    span?.setAttributes({
                        'vector.results': data.length,
                    });
                }
                break;
            }
            case 'agentuity.vector.get': {
                if (result.response.status === 404) {
                    span?.addEvent('miss');
                }
                else if (result.response.ok) {
                    span?.addEvent('hit');
                }
                break;
            }
        }
    },
}, logger);
let kv;
let stream;
let vector;
let sandbox;
let queue;
let email;
let schedule;
let task;
let session;
let thread;
let sessionEvent;
let evalRunEvent;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let localRouter = null;
const ServerUrlMissingError = StructuredError('ServerUrlMissingError', 'serverUrl is required when using local services');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function createServices(logger, config, serverUrl) {
    const authenticated = isAuthenticated();
    const useLocal = config?.services?.useLocal ?? false;
    adapter = createFetchAdapter(logger);
    // Use local services if explicitly requested OR if not authenticated
    const shouldUseLocal = useLocal || !authenticated || process.env.AGENTUITY_FORCE_LOCAL_SERVICES === 'true';
    if (shouldUseLocal) {
        const db = getLocalDB();
        const projectPath = normalizeProjectPath();
        if (!serverUrl) {
            throw new ServerUrlMissingError();
        }
        if (!localServicesLogged.get()) {
            localServicesLogged.set(true);
            logger.info('Using local services (development only)');
        }
        kv = config?.services?.keyvalue || new LocalKeyValueStorage(db, projectPath);
        stream = config?.services?.stream || new LocalStreamStorage(db, projectPath, serverUrl);
        vector = config?.services?.vector || new LocalVectorStorage(db, projectPath);
        queue = new LocalQueueStorage(db, projectPath);
        email = config?.services?.email || new LocalEmailStorage();
        task = config?.services?.task || new LocalTaskStorage(db, projectPath);
        session = config?.services?.session || new DefaultSessionProvider();
        thread = config?.services?.thread || new LocalThreadProvider();
        sessionEvent = config?.services?.sessionEvent
            ? new CompositeSessionEventProvider(new LocalSessionEventProvider(), config.services.sessionEvent)
            : new LocalSessionEventProvider();
        evalRunEvent = config?.services?.evalRunEvent
            ? new CompositeEvalRunEventProvider(new LocalEvalRunEventProvider(), config.services.evalRunEvent)
            : new LocalEvalRunEventProvider();
        localRouter = createLocalStorageRouter(db, projectPath);
        return { localRouter };
    }
    // Reset local router if not using local services
    localRouter = null;
    // At this point we must be authenticated (since !authenticated would trigger local services above)
    const catalystUrl = getCatalystBaseUrl();
    const streamBaseUrl = getStreamBaseUrl();
    kv = config?.services?.keyvalue || new KeyValueStorageService(getKvBaseUrl(), adapter);
    stream = config?.services?.stream || new StreamStorageService(streamBaseUrl, adapter);
    vector = config?.services?.vector || new VectorStorageService(getVectorBaseUrl(), adapter);
    queue = new QueueStorageService(getQueueBaseUrl(), adapter);
    email = config?.services?.email || new EmailStorageService(getEmailBaseUrl(), adapter);
    task = config?.services?.task || new TaskStorageService(getTaskBaseUrl(), adapter);
    sandbox = new HTTPSandboxService(new APIClient(catalystUrl, logger), streamBaseUrl);
    schedule = new ScheduleService(getCatalystBaseUrl(), adapter);
    session = config?.services?.session || new DefaultSessionProvider();
    thread = config?.services?.thread || new DefaultThreadProvider();
    // FIXME: this is turned off for now for production until we have the new changes deployed
    sessionEvent =
        isProduction() && process.env.AGENTUITY_CLOUD_EXPORT_DIR
            ? new JSONSessionEventProvider(process.env.AGENTUITY_CLOUD_EXPORT_DIR)
            : new HTTPSessionEventProvider(new APIClient(catalystUrl, logger), logger);
    if (config?.services?.sessionEvent) {
        sessionEvent = new CompositeSessionEventProvider(sessionEvent, config.services.sessionEvent);
    }
    // FIXME: this is turned off for now for production until we have the new changes deployed
    logger.debug('[SERVICES] Initializing eval run provider - region: %s, catalystBaseUrl: %s, isProduction: %s', getRegion(), catalystUrl, isProduction());
    evalRunEvent =
        isProduction() && process.env.AGENTUITY_CLOUD_EXPORT_DIR
            ? new JSONEvalRunEventProvider(process.env.AGENTUITY_CLOUD_EXPORT_DIR)
            : new HTTPEvalRunEventProvider(new APIClient(catalystUrl, logger), logger, catalystUrl);
    if (config?.services?.evalRunEvent) {
        evalRunEvent = new CompositeEvalRunEventProvider(evalRunEvent, config.services.evalRunEvent);
    }
    return {};
}
export function getThreadProvider() {
    return thread;
}
export function getSessionProvider() {
    return session;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function getLocalRouter() {
    return localRouter;
}
export function getSessionEventProvider() {
    return sessionEvent;
}
export function getEvalRunEventProvider() {
    return evalRunEvent;
}
export function getServices() {
    return { kv, stream, vector, sandbox, queue, email, schedule, task };
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function registerServices(o, includeAgents = false) {
    Object.defineProperty(o, 'kv', {
        get: () => kv,
        enumerable: false,
        configurable: false,
    });
    Object.defineProperty(o, 'stream', {
        get: () => stream,
        enumerable: false,
        configurable: false,
    });
    Object.defineProperty(o, 'vector', {
        get: () => vector,
        enumerable: false,
        configurable: false,
    });
    Object.defineProperty(o, 'sandbox', {
        get: () => sandbox,
        enumerable: false,
        configurable: false,
    });
    Object.defineProperty(o, 'queue', {
        get: () => queue,
        enumerable: false,
        configurable: false,
    });
    Object.defineProperty(o, 'email', {
        get: () => email,
        enumerable: false,
        configurable: false,
    });
    Object.defineProperty(o, 'schedule', {
        get: () => schedule,
        enumerable: false,
        configurable: false,
    });
    Object.defineProperty(o, 'task', {
        get: () => task,
        enumerable: false,
        configurable: false,
    });
    // Also register agent registry if requested
    if (includeAgents) {
        // Cache the populated registry to avoid re-creating on every access
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let cachedRegistry;
        Object.defineProperty(o, 'agent', {
            get: () => {
                if (!cachedRegistry) {
                    // ESM-friendly: populateAgentsRegistry is imported statically
                    cachedRegistry = populateAgentsRegistry(o);
                }
                return cachedRegistry;
            },
            enumerable: false,
            configurable: false,
        });
    }
}
//# sourceMappingURL=_services.js.map