import { ServiceContainer } from '../container/ServiceContainer.js';
import { Kernel } from '../http/Kernel.js';
import { Request } from '../http/Request.js';
import { Response } from '../http/Response.js';
import { Router } from '../routes/Router.js';
const lowerCaseHeaders = (headers) => {
    if (!headers)
        return {};
    const out = {};
    for (const [k, v] of Object.entries(headers))
        out[k.toLowerCase()] = v;
    return out;
};
const createNodeResStub = () => {
    const headers = {};
    let body = '';
    const raw = {
        statusCode: 200,
        writableEnded: false,
        setHeader(name, value) {
            headers[name] = value;
        },
        end(data) {
            raw.writableEnded = true;
            if (data === undefined)
                return;
            body += Buffer.isBuffer(data) ? data.toString('utf8') : String(data);
        },
    };
    return { raw, headers, getBody: () => body };
};
const parseJsonBody = (raw) => {
    try {
        return JSON.parse(raw);
    }
    catch {
        return raw;
    }
};
const cookiesFromSetCookieHeader = (setCookie) => {
    const values = [];
    if (Array.isArray(setCookie)) {
        values.push(...setCookie.map(String));
    }
    else if (setCookie !== undefined && setCookie !== null) {
        values.push(String(setCookie));
    }
    const result = {};
    for (const cookie of values) {
        const firstPart = cookie.split(';')[0] ?? '';
        const idx = firstPart.indexOf('=');
        if (idx <= 0)
            continue;
        const name = firstPart.slice(0, idx).trim();
        const value = firstPart.slice(idx + 1).trim();
        if (name !== '')
            result[name] = value;
    }
    return result;
};
const createOverridableContainer = (base) => {
    const overrides = new Map();
    const swapSingleton = (key, instance) => {
        overrides.set(key, { kind: 'singleton', value: instance });
        return () => {
            overrides.delete(key);
        };
    };
    const swapFactory = (key, factory) => {
        overrides.set(key, { kind: 'factory', factory });
        return () => {
            overrides.delete(key);
        };
    };
    return {
        bind: (key, factory) => base.bind(key, factory),
        singleton: (key, factoryOrInstance) => base.singleton(key, factoryOrInstance),
        resolve: (key) => {
            const override = overrides.get(key);
            if (override) {
                return (override.kind === 'singleton' ? override.value : override.factory());
            }
            return base.resolve(key);
        },
        has: (key) => overrides.has(key) || base.has(key),
        get: (key) => {
            const override = overrides.get(key);
            if (override) {
                return (override.kind === 'singleton' ? override.value : override.factory());
            }
            return base.get(key);
        },
        flush: () => {
            overrides.clear();
            base.flush();
        },
        swapSingleton,
        swapFactory,
    };
};
export const TestEnvironment = Object.freeze({
    create(options = {}) {
        const router = options.router ?? Router.createRouter();
        if (typeof options.registerRoutes === 'function')
            options.registerRoutes(router);
        const baseContainer = options.container ?? ServiceContainer.create();
        const container = createOverridableContainer(baseContainer);
        const kernel = Kernel.create(router, container);
        return {
            router,
            container,
            kernel,
            async request(input) {
                const nodeReq = {
                    method: input.method,
                    url: input.path,
                    headers: lowerCaseHeaders(input.headers),
                };
                const nodeRes = createNodeResStub();
                const req = Request.create(nodeReq);
                if (input.body !== undefined)
                    req.setBody(input.body);
                if (input.validated !== undefined)
                    req.validated = input.validated;
                const res = Response.create(nodeRes.raw);
                await kernel.handleRequest(req, res);
                const bodyText = nodeRes.getBody();
                const headers = nodeRes.headers;
                return {
                    status: nodeRes.raw.statusCode,
                    headers,
                    bodyText,
                    json: parseJsonBody(bodyText),
                    cookies: cookiesFromSetCookieHeader(headers['Set-Cookie']),
                };
            },
            swapSingleton: container.swapSingleton,
            swapFactory: container.swapFactory,
        };
    },
});
