#!/usr/bin/env node
/*******************************************************************************
 * AuthDB Node.js Client Bindings – HTTP Test Application
 *
 * A small HTTP server that exposes the authdb client bindings as a REST API
 * so you can test every method from a browser or with curl.
 *
 * Usage:
 *   node test-server.js [port]            (default port: 3000)
 *
 * Environment variables (or set via the /configure endpoint):
 *   AUTHDB_URL          – AuthDB server URL  (e.g. https://localhost:9090)
 *   AUTHDB_CLIENT_NAME  – client name
 *   AUTHDB_CLIENT_SECRET– client secret
 *   AUTHDB_USERNAME     – user name
 *   AUTHDB_PASSWORD     – user password
 ******************************************************************************/

'use strict';

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

// ---------- load native binding for the current ABI --------------------------
const abiVersion = process.versions.modules;
const modulePath = path.join(__dirname, '..', 'build', 'bindings',
                             `authdb-nds.abi-${abiVersion}.node`);

let authdb;
try {
    authdb = require(modulePath);
    console.log(`[authdb] Loaded native module (ABI ${abiVersion})`);
} catch (e) {
    console.error(`[authdb] Failed to load ${modulePath}: ${e.message}`);
    process.exit(1);
}

// ---------- shared state -----------------------------------------------------
let config = {
    url:          process.env.AUTHDB_URL           || '',
    clientName:   process.env.AUTHDB_CLIENT_NAME   || '',
    clientSecret: process.env.AUTHDB_CLIENT_SECRET || '',
    username:     process.env.AUTHDB_USERNAME       || '',
    password:     process.env.AUTHDB_PASSWORD       || ''
};

// ---------- helpers ----------------------------------------------------------
function jsonResponse(res, statusCode, obj) {
    const body = JSON.stringify(obj, null, 2);
    res.writeHead(statusCode, {
        'Content-Type':  'application/json',
        'Content-Length': Buffer.byteLength(body)
    });
    res.end(body);
}

function readBody(req) {
    return new Promise((resolve, reject) => {
        const chunks = [];
        req.on('data',  c  => chunks.push(c));
        req.on('end',   () => resolve(Buffer.concat(chunks).toString()));
        req.on('error', reject);
    });
}

function makeConnection() {
    const con = new authdb.ClientConnection();
    if (config.url)          con.setUrl(config.url);
    if (config.clientName)   con.setClientName(config.clientName);
    if (config.clientSecret) con.setClientSecret(config.clientSecret);
    if (config.username)     con.setUsername(config.username);
    if (config.password)     con.setUserPassword(config.password);
    return con;
}

// ---------- route handlers ---------------------------------------------------
const routes = {};

// GET /wizard – serve the test wizard HTML page
routes['GET /wizard'] = (_req, res) => {
    const htmlPath = path.join(__dirname, 'wizard.html');
    fs.readFile(htmlPath, 'utf8', (err, data) => {
        if (err) {
            res.writeHead(500, { 'Content-Type': 'text/plain' });
            res.end('Failed to load wizard.html: ' + err.message);
            return;
        }
        res.writeHead(200, {
            'Content-Type': 'text/html; charset=utf-8',
            'Content-Length': Buffer.byteLength(data)
        });
        res.end(data);
    });
};

// GET / – overview
routes['GET /'] = (_req, res) => {
    const endpoints = Object.keys(routes).sort().map(k => {
        const [method, path] = k.split(' ', 2);
        return { method, path };
    });
    jsonResponse(res, 200, {
        service: 'AuthDB Node.js Binding Tester',
        abi:      abiVersion,
        config:  { ...config, password: config.password ? '***' : '' },
        endpoints
    });
};

// POST /configure – set connection parameters at runtime
// Body: { "url": "...", "clientName": "...", "clientSecret": "...",
//         "username": "...", "password": "..." }
routes['POST /configure'] = async (req, res) => {
    const body = JSON.parse(await readBody(req));
    if (body.url !== undefined)          config.url          = body.url;
    if (body.clientName !== undefined)   config.clientName   = body.clientName;
    if (body.clientSecret !== undefined) config.clientSecret = body.clientSecret;
    if (body.username !== undefined)     config.username     = body.username;
    if (body.password !== undefined)     config.password     = body.password;
    jsonResponse(res, 200, { status: 'ok', config: { ...config, password: '***' } });
};

// POST /user-auth – authenticate as user
routes['POST /user-auth'] = (_req, res) => {
    const con = makeConnection();
    const client = new authdb.Client(con);
    const ok = client.userAuth();
    jsonResponse(res, 200, {
        action:    'userAuth',
        success:   ok,
        sessionId: con.getSessionID()
    });
};

// POST /client-auth – authenticate as client
routes['POST /client-auth'] = (_req, res) => {
    const con = makeConnection();
    const client = new authdb.Client(con);
    const ok = client.clientAuth();
    jsonResponse(res, 200, {
        action:    'clientAuth',
        success:   ok,
        clientId:  con.getClientId(),
        sessionId: con.getSessionID()
    });
};

// POST /full-auth – client auth + user auth combined
routes['POST /full-auth'] = (_req, res) => {
    const con = makeConnection();
    const client = new authdb.Client(con);
    const clientOk = client.clientAuth();
    let userOk = false;
    if (clientOk) {
        userOk = client.userAuth();
    }
    jsonResponse(res, 200, {
        action:    'fullAuth',
        clientAuth: clientOk,
        userAuth:   userOk,
        clientId:   con.getClientId(),
        sessionId:  con.getSessionID()
    });
};

// GET /session-info – get session information (requires prior auth)
routes['POST /session-info'] = (_req, res) => {
    const con = makeConnection();
    const client = new authdb.Client(con);
    const clientOk = client.clientAuth();
    if (!clientOk) {
        jsonResponse(res, 401, { error: 'clientAuth failed' });
        return;
    }
    const userOk = client.userAuth();
    if (!userOk) {
        jsonResponse(res, 401, { error: 'userAuth failed' });
        return;
    }
    const info = client.sessionInfo();
    jsonResponse(res, 200, { action: 'sessionInfo', ...info });
};

// POST /rem-session – remove session
routes['POST /rem-session'] = (_req, res) => {
    const con = makeConnection();
    const client = new authdb.Client(con);
    if (!client.clientAuth() || !client.userAuth()) {
        jsonResponse(res, 401, { error: 'auth failed' });
        return;
    }
    const ok = client.remSession();
    jsonResponse(res, 200, { action: 'remSession', success: ok });
};

// POST /gpo-check – check GPO for current user session
// Body: { "gpoId": "..." }
routes['POST /gpo-check'] = async (req, res) => {
    const body = JSON.parse(await readBody(req));
    if (!body.gpoId) {
        jsonResponse(res, 400, { error: 'gpoId required' });
        return;
    }
    const con = makeConnection();
    const client = new authdb.Client(con);
    if (!client.clientAuth() || !client.userAuth()) {
        jsonResponse(res, 401, { error: 'auth failed' });
        return;
    }
    const ok = client.gpoCheck(body.gpoId);
    jsonResponse(res, 200, { action: 'gpoCheck', gpoId: body.gpoId, result: ok });
};

// POST /gpo-exists – check if GPO exists (client-auth only)
// Body: { "gpoId": "..." }
routes['POST /gpo-exists'] = async (req, res) => {
    const body = JSON.parse(await readBody(req));
    if (!body.gpoId) {
        jsonResponse(res, 400, { error: 'gpoId required' });
        return;
    }
    const con = makeConnection();
    const client = new authdb.Client(con);
    if (!client.clientAuth()) {
        jsonResponse(res, 401, { error: 'clientAuth failed' });
        return;
    }
    const ok = client.gpoExists(body.gpoId);
    jsonResponse(res, 200, { action: 'gpoExists', gpoId: body.gpoId, result: ok });
};

// POST /add-connection – add a client connection
// Body: { "clientName": "...", "clientSecret": "..." }
routes['POST /add-connection'] = async (req, res) => {
    const body = JSON.parse(await readBody(req));
    if (!body.clientName || !body.clientSecret) {
        jsonResponse(res, 400, { error: 'clientName and clientSecret required' });
        return;
    }
    const con = makeConnection();
    const client = new authdb.Client(con);
    if (!client.userAuth()) {
        jsonResponse(res, 401, { error: 'userAuth failed' });
        return;
    }
    const clientDbId = client.addConnection(body.clientName, body.clientSecret);
    jsonResponse(res, 200, { action: 'addConnection', clientName: body.clientName, clientDbId });
};

// POST /rem-connection – remove a client connection
// Body: { "clientId": "..." }
routes['POST /rem-connection'] = async (req, res) => {
    const body = JSON.parse(await readBody(req));
    if (!body.clientId) {
        jsonResponse(res, 400, { error: 'clientId required' });
        return;
    }
    const con = makeConnection();
    const client = new authdb.Client(con);
    if (!client.userAuth()) {
        jsonResponse(res, 401, { error: 'userAuth failed' });
        return;
    }
    client.remConnection(body.clientId);
    jsonResponse(res, 200, { action: 'remConnection', clientId: body.clientId });
};

// POST /edit-connection – edit a client connection
// Body: { "clientId": "...", "clientName": "...", "clientSecret": "..." }
routes['POST /edit-connection'] = async (req, res) => {
    const body = JSON.parse(await readBody(req));
    if (!body.clientId || !body.clientName || !body.clientSecret) {
        jsonResponse(res, 400, { error: 'clientId, clientName, clientSecret required' });
        return;
    }
    const con = makeConnection();
    const client = new authdb.Client(con);
    if (!client.userAuth()) {
        jsonResponse(res, 401, { error: 'userAuth failed' });
        return;
    }
    client.editConnection(body.clientId, body.clientName, body.clientSecret);
    jsonResponse(res, 200, {
        action: 'editConnection',
        clientId: body.clientId, clientName: body.clientName
    });
};

// POST /change-password – change user password
// Body: { "oldPassword": "...", "newPassword": "..." }
routes['POST /change-password'] = async (req, res) => {
    const body = JSON.parse(await readBody(req));
    if (!body.oldPassword || !body.newPassword) {
        jsonResponse(res, 400, { error: 'oldPassword and newPassword required' });
        return;
    }
    const con = makeConnection();
    const client = new authdb.Client(con);
    if (!client.clientAuth() || !client.userAuth()) {
        jsonResponse(res, 401, { error: 'auth failed' });
        return;
    }
    client.changeUserPw(body.oldPassword, body.newPassword);
    jsonResponse(res, 200, { action: 'changeUserPw', status: 'ok' });
};

// GET /connection-ids – show current connection IDs
routes['GET /connection-ids'] = (_req, res) => {
    const con = makeConnection();
    jsonResponse(res, 200, {
        clientId:  con.getClientId(),
        sessionId: con.getSessionID()
    });
};

// POST /test-wizard – full integration test sequence
// Body (optional): { "testClientName": "...", "testClientSecret": "...", "gpoId": "..." }
routes['POST /test-wizard'] = async (req, res) => {
    const bodyRaw = await readBody(req);
    const body = bodyRaw ? JSON.parse(bodyRaw) : {};
    const suffix = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
    const testClientName   = body.testClientName   || ('wiz-' + suffix);
    const testClientSecret = body.testClientSecret || ('secret-' + suffix);
    const gpoId            = body.gpoId            || null;

    const steps = [];
    let overallSuccess = true;

    function step(name, fn) {
        const t0 = Date.now();
        try {
            const result = fn();
            steps.push({ step: name, success: true, ms: Date.now() - t0, result });
            return result;
        } catch (e) {
            overallSuccess = false;
            steps.push({ step: name, success: false, ms: Date.now() - t0, error: e.message });
            return null;
        }
    }

    // ---- Step 1: User Auth ----
    const con1 = makeConnection();
    const client1 = new authdb.Client(con1);
    const userAuthOk = step('1_user_auth', () => {
        const ok = client1.userAuth();
        return { ok, sessionId: con1.getSessionID() };
    });

    if (!userAuthOk || !userAuthOk.ok) {
        jsonResponse(res, 200, { wizard: 'ABORTED', reason: 'user auth failed', steps });
        return;
    }

    // ---- Step 2: Add Connection (create client) ----
    let clientDbId = null;
    step('2_add_connection', () => {
        clientDbId = client1.addConnection(testClientName, testClientSecret);
        return { clientName: testClientName, clientDbId };
    });

    // ---- Step 3: Client Auth (with new client to get clientId) ----
    const con2 = makeConnection();
    con2.setClientName(testClientName);
    con2.setClientSecret(testClientSecret);
    const client2 = new authdb.Client(con2);

    const clientAuthOk = step('3_client_auth', () => {
        const ok = client2.clientAuth();
        return { ok, clientId: con2.getClientId() };
    });

    if (!clientAuthOk || !clientAuthOk.ok) {
        jsonResponse(res, 200, { wizard: 'ABORTED', reason: 'client auth failed', steps });
        return;
    }

    const clientId = con2.getClientId();

    // ---- Step 4: User Auth (under the new client connection) ----
    const userAuth2Ok = step('4_user_auth_with_client', () => {
        const ok = client2.userAuth();
        return { ok, sessionId: con2.getSessionID() };
    });

    if (!userAuth2Ok || !userAuth2Ok.ok) {
        jsonResponse(res, 200, { wizard: 'ABORTED', reason: 'user auth (with client) failed', steps });
        return;
    }

    // ---- Step 5: Session Info ----
    step('5_session_info', () => {
        const info = client2.sessionInfo();
        return info;
    });

    // ---- Step 6: GPO Check (optional) ----
    if (gpoId) {
        step('6_gpo_check', () => {
            const ok = client2.gpoCheck(gpoId);
            return { gpoId, result: ok };
        });
    } else {
        steps.push({ step: '6_gpo_check', success: true, ms: 0, result: 'skipped (no gpoId provided)' });
    }

    // ---- Step 7: Remove Connection ----
    // Need a fresh user-auth-only connection for remConnection
    const con3 = makeConnection();
    const client3 = new authdb.Client(con3);
    step('7_rem_connection', () => {
        const ok = client3.userAuth();
        if (!ok) throw new Error('user auth for rem-connection failed');
        client3.remConnection(clientDbId);
        return { removedClientId: clientDbId };
    });

    // ---- Step 8: Verify client is gone (auth should fail) ----
    step('8_verify_removed', () => {
        const con4 = makeConnection();
        con4.setClientName(testClientName);
        con4.setClientSecret(testClientSecret);
        const client4 = new authdb.Client(con4);
        const ok = client4.clientAuth();
        if (ok) throw new Error('client still exists after removal!');
        return { clientGone: true };
    });

    jsonResponse(res, 200, {
        wizard: overallSuccess ? 'ALL PASSED' : 'SOME FAILED',
        totalSteps: steps.length,
        passed: steps.filter(s => s.success).length,
        failed: steps.filter(s => !s.success).length,
        steps
    });
};

// ---------- HTTP server ------------------------------------------------------
const server = http.createServer(async (req, res) => {
    const parsed  = url.parse(req.url, true);
    const routeKey = `${req.method} ${parsed.pathname}`;

    console.log(`[${new Date().toISOString()}] ${req.method} ${parsed.pathname}`);

    const handler = routes[routeKey];
    if (!handler) {
        jsonResponse(res, 404, { error: 'not found', path: parsed.pathname });
        return;
    }

    try {
        await handler(req, res, parsed.query);
    } catch (e) {
        console.error(`[error] ${routeKey}: ${e.message}`);
        jsonResponse(res, 500, { error: e.message });
    }
});

const PORT = parseInt(process.argv[2] || '3000', 10);
server.listen(PORT, '0.0.0.0', () => {
    console.log(`[authdb-test] listening on http://0.0.0.0:${PORT}`);
    console.log(`[authdb-test] GET / for endpoint list`);
});
