// Tiny Docker Engine API client over the bind-mounted unix socket.
//
// Extracted from service-routes.js so other routes (per-container stats,
// system df, etc.) can talk to the daemon without duplicating the socket-
// discovery + http-over-unix-socket dance.
//
// We deliberately do NOT add the `dockerode` package — node's built-in http
// agent already supports `socketPath`, and the small subset of the Engine
// API we use fits in a couple of dozen lines. Zero extra deps, easy to audit.

const fs = require('fs');
const http = require('http');

// Whichever socket the host bind-mounted into the container is the one we
// can reach. Rooted installs mount /var/run/docker.sock; rootless mounts
// /run/user/<uid>/docker.sock under the runtime dir.
function detectDockerSocket() {
    if (fs.existsSync('/var/run/docker.sock')) return '/var/run/docker.sock';
    try {
        for (const entry of fs.readdirSync('/run/user', { withFileTypes: true })) {
            if (!entry.isDirectory()) continue;
            const sock = `/run/user/${entry.name}/docker.sock`;
            if (fs.existsSync(sock)) return sock;
        }
    } catch { /* /run/user not readable — fine */ }
    return null;
}

const DOCKER_SOCKET = detectDockerSocket();
const DOCKER_API_VERSION = 'v1.41';   // Docker 20.10+

// Simple JSON GET (or other method without a body). Returns parsed JSON.
function dockerRequest(method, pathname, query) {
    return new Promise((resolve, reject) => {
        if (!DOCKER_SOCKET) return reject(new Error('No docker socket available'));
        const qs = query ? '?' + new URLSearchParams(query).toString() : '';
        const req = http.request(
            {
                socketPath: DOCKER_SOCKET,
                method,
                path: `/${DOCKER_API_VERSION}${pathname}${qs}`,
                headers: { Host: 'docker', Accept: 'application/json' },
            },
            (res) => {
                const chunks = [];
                res.on('data', (c) => chunks.push(c));
                res.on('end', () => {
                    const body = Buffer.concat(chunks).toString('utf8');
                    if (res.statusCode >= 200 && res.statusCode < 300) {
                        try { resolve(body ? JSON.parse(body) : null); }
                        catch (e) { reject(new Error(`Docker API parse error: ${e.message}`)); }
                    } else {
                        reject(new Error(`Docker API ${res.statusCode}: ${body}`));
                    }
                });
            }
        );
        req.on('error', reject);
        req.end();
    });
}

// Streaming GET — caller gets the raw IncomingMessage so they can pipe
// or parse multiplexed log frames themselves.
function dockerStream(pathname, query) {
    return new Promise((resolve, reject) => {
        if (!DOCKER_SOCKET) return reject(new Error('No docker socket available'));
        const qs = query ? '?' + new URLSearchParams(query).toString() : '';
        const req = http.request(
            {
                socketPath: DOCKER_SOCKET,
                method: 'GET',
                path: `/${DOCKER_API_VERSION}${pathname}${qs}`,
                headers: { Host: 'docker' },
            },
            (res) => {
                if (res.statusCode >= 200 && res.statusCode < 300) {
                    resolve({ stream: res, req });
                } else {
                    const chunks = [];
                    res.on('data', (c) => chunks.push(c));
                    res.on('end', () => reject(new Error(
                        `Docker API ${res.statusCode}: ${Buffer.concat(chunks).toString('utf8')}`
                    )));
                }
            }
        );
        req.on('error', reject);
        req.end();
    });
}

// Docker's log frames over the API are multiplexed when no TTY is attached.
// Each frame: 8-byte header [stream(1) 0 0 0 size(4 BE)] + N payload bytes.
// stream: 0=stdin (unused), 1=stdout, 2=stderr. This decoder concatenates
// the payload as a single string with no markers (callers don't care about
// per-stream tagging for our use cases — they just want the text). If a
// container WAS started with -t, frames are raw text with no header; we
// detect that by failing to parse a sane header and falling back to a raw
// utf-8 decode.
function decodeMultiplexedLog(buf) {
    if (!Buffer.isBuffer(buf) || buf.length === 0) return '';
    const out = [];
    let i = 0;
    let sawValidFrame = false;
    while (i + 8 <= buf.length) {
        const stream = buf[i];
        if (stream > 2) break;   // not a header — bail to raw fallback
        const size = buf.readUInt32BE(i + 4);
        const end = i + 8 + size;
        if (end > buf.length) break;
        out.push(buf.slice(i + 8, end).toString('utf8'));
        sawValidFrame = true;
        i = end;
    }
    if (!sawValidFrame) return buf.toString('utf8');
    return out.join('');
}

module.exports = {
    DOCKER_SOCKET,
    DOCKER_API_VERSION,
    dockerRequest,
    dockerStream,
    decodeMultiplexedLog,
};
