// Shared CARD API helpers
// Centralizes HTTP calls for the CARD asset ownership API.
// Follows the same promise-based pattern as atlasApi.js, with the addition
// of OAuth Bearer token management (auto-acquire, cache, refresh, 401 retry).
//
// CARD API versioning:
//   - Read endpoints (GET):    /api/v1/...
//   - Mutation endpoints (POST): /api/v2/...

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

// ---------------------------------------------------------------------------
// Configuration — read from process.env at module load
// ---------------------------------------------------------------------------
const CARD_API_URL  = process.env.CARD_API_URL  || '';
const CARD_API_USER = process.env.CARD_API_USER || '';
const CARD_API_PASS = process.env.CARD_API_PASS || '';
const CARD_SKIP_TLS = process.env.CARD_SKIP_TLS === 'true';

const requiredVars = ['CARD_API_URL', 'CARD_API_USER', 'CARD_API_PASS'];
const missingVars  = requiredVars.filter((v) => !process.env[v]);
if (missingVars.length > 0) {
    console.warn(`[card-api] WARNING: Missing required environment variables: ${missingVars.join(', ')}. CARD API calls will fail.`);
}

const isConfigured = missingVars.length === 0;

// ---------------------------------------------------------------------------
// Token Manager — OAuth Bearer token with 1-hour TTL
// ---------------------------------------------------------------------------
let cachedToken  = null;   // { token: string, expiresAt: number (epoch ms) }

function tokenIsValid() {
    if (!cachedToken) return false;
    // Refresh if within 60 seconds of expiry
    return cachedToken.expiresAt - Date.now() > 60_000;
}

function invalidateToken() {
    cachedToken = null;
}

/**
 * Acquire a new Bearer token from CARD /api/v1/auth/get_token using Basic Auth.
 * Caches the token in memory with a 1-hour TTL.
 */
function acquireToken(timeout) {
    const authString = Buffer.from(CARD_API_USER + ':' + CARD_API_PASS).toString('base64');
    const fullUrl    = new URL(CARD_API_URL + '/api/v1/auth/get_token');
    const isHttps    = fullUrl.protocol === 'https:';
    const transport  = isHttps ? https : http;

    return new Promise((resolve, reject) => {
        const reqOptions = {
            hostname: fullUrl.hostname,
            port:     fullUrl.port || (isHttps ? 443 : 80),
            path:     fullUrl.pathname + fullUrl.search,
            method:   'POST',
            headers:  {
                'accept':        'application/json',
                'authorization': 'Basic ' + authString,
                'content-length': '0',
            },
            timeout: timeout || 15000,
        };

        if (isHttps) {
            reqOptions.rejectUnauthorized = !CARD_SKIP_TLS;
        }

        const req = transport.request(reqOptions, (res) => {
            let data = '';
            res.on('data', (chunk) => { data += chunk; });
            res.on('end', () => {
                if (res.statusCode < 200 || res.statusCode >= 300) {
                    return reject(new Error(
                        `[card-api] Token acquisition failed with HTTP ${res.statusCode}: ${data.substring(0, 500)}`
                    ));
                }

                // The CARD API returns the token as a JSON string or object.
                // Try to parse; fall back to raw body as the token string.
                let token;
                try {
                    const parsed = JSON.parse(data);
                    token = typeof parsed === 'string' ? parsed
                          : parsed.token || parsed.access_token || data.trim();
                } catch (_) {
                    // Response may be a plain token string (unquoted)
                    token = data.trim();
                }

                if (!token) {
                    return reject(new Error('[card-api] Token parse failure: empty token in response body.'));
                }

                cachedToken = {
                    token,
                    expiresAt: Date.now() + 60 * 60 * 1000, // 1-hour TTL
                };
                resolve(cachedToken.token);
            });
        });

        req.on('timeout', () => req.destroy(new Error('GET /api/v1/auth/get_token timed out')));
        req.on('error', (err) => {
            reject(new Error(`[card-api] GET /api/v1/auth/get_token failed: ${err.message}`));
        });
        req.end();
    });
}

/**
 * Ensure we have a valid Bearer token, acquiring or refreshing as needed.
 */
async function ensureToken(timeout) {
    if (tokenIsValid()) return cachedToken.token;
    return acquireToken(timeout);
}

// ---------------------------------------------------------------------------
// Generic request — supports GET and POST with Bearer auth + 401 retry
// ---------------------------------------------------------------------------
async function cardRequest(method, urlPath, body, options) {
    const timeout = (options && options.timeout) || 15000;
    const skipAuth = (options && options.skipAuth) || false;

    async function doRequest(bearerToken) {
        const fullUrl   = new URL(CARD_API_URL + urlPath);
        const isHttps   = fullUrl.protocol === 'https:';
        const transport = isHttps ? https : http;

        const headers = { 'accept': 'application/json' };

        if (bearerToken) {
            headers['authorization'] = 'Bearer ' + bearerToken;
        }

        let bodyStr = null;
        if (body !== null && body !== undefined) {
            bodyStr = JSON.stringify(body);
            headers['content-type']   = 'application/json';
            headers['content-length'] = Buffer.byteLength(bodyStr);
        }

        return new Promise((resolve, reject) => {
            const reqOptions = {
                hostname: fullUrl.hostname,
                port:     fullUrl.port || (isHttps ? 443 : 80),
                path:     fullUrl.pathname + fullUrl.search,
                method,
                headers,
                timeout,
            };

            if (isHttps) {
                reqOptions.rejectUnauthorized = !CARD_SKIP_TLS;
            }

            const req = transport.request(reqOptions, (res) => {
                let data = '';
                res.on('data', (chunk) => { data += chunk; });
                res.on('end', () => resolve({ status: res.statusCode, body: data }));
            });

            req.on('timeout', () => req.destroy(new Error(`${method} ${urlPath} timed out`)));
            req.on('error', (err) => {
                reject(new Error(`[card-api] ${method} ${urlPath} failed: ${err.message}`));
            });

            if (bodyStr) req.write(bodyStr);
            req.end();
        });
    }

    // Skip auth for the token endpoint itself
    if (skipAuth) {
        return doRequest(null);
    }

    // Normal flow: ensure token → request → retry once on 401
    let token = await ensureToken(timeout);
    let result = await doRequest(token);

    if (result.status === 401) {
        // Invalidate and retry exactly once
        invalidateToken();
        token = await ensureToken(timeout);
        result = await doRequest(token);
    }

    return result;
}

// ---------------------------------------------------------------------------
// Convenience wrappers
// ---------------------------------------------------------------------------
function cardGet(urlPath, options) {
    return cardRequest('GET', urlPath, null, options);
}

function cardPost(urlPath, body, options) {
    return cardRequest('POST', urlPath, body, options);
}

// ---------------------------------------------------------------------------
// High-level helpers used by the UAT test and routes
// ---------------------------------------------------------------------------

/**
 * Test connection by acquiring a token. Returns { ok, token } or { ok, error }.
 */
async function testConnection() {
    try {
        const token = await acquireToken();
        return { ok: true, token: token.substring(0, 12) + '...' };
    } catch (err) {
        return { ok: false, error: err.message };
    }
}

/**
 * GET /api/v1/teams — list all CARD teams.
 */
async function getTeams() {
    const res = await cardGet('/api/v1/teams');
    return { status: res.status, body: res.body, ok: res.status >= 200 && res.status < 300 };
}

/**
 * GET /api/v1/team/{teamName}/assets — list assets for a team.
 */
async function getTeamAssets(teamName, { disposition, page, pageSize } = {}) {
    const params = new URLSearchParams();
    if (disposition) params.set('disposition', disposition);
    if (page)        params.set('page', String(page));
    params.set('page_size', String(pageSize || 50));

    const qs = params.toString();
    const res = await cardGet(`/api/v1/team/${encodeURIComponent(teamName)}/assets${qs ? '?' + qs : ''}`);
    return { status: res.status, body: res.body, ok: res.status >= 200 && res.status < 300 };
}

/**
 * GET /api/v1/owner/{assetId} — get owner record including update_token.
 */
async function getOwner(assetId) {
    const res = await cardGet(`/api/v1/owner/${encodeURIComponent(assetId)}`);
    return { status: res.status, body: res.body, ok: res.status >= 200 && res.status < 300 };
}

/**
 * POST /api/v2/owner/{assetId}/confirm — confirm asset to a team.
 */
async function confirmAsset(assetId, teamName, updateToken, comment) {
    const params = new URLSearchParams({ update_token: updateToken });
    if (comment) params.set('comment', comment);
    const res = await cardPost(
        `/api/v2/owner/${encodeURIComponent(assetId)}/confirm?${params.toString()}`,
        { name: teamName }
    );
    return { status: res.status, body: res.body, ok: res.status >= 200 && res.status < 300 };
}

/**
 * POST /api/v2/owner/{assetId}/decline — decline asset from a team.
 */
async function declineAsset(assetId, teamName, updateToken, comment) {
    const params = new URLSearchParams({ update_token: updateToken });
    if (comment) params.set('comment', comment);
    const res = await cardPost(
        `/api/v2/owner/${encodeURIComponent(assetId)}/decline?${params.toString()}`,
        { name: teamName }
    );
    return { status: res.status, body: res.body, ok: res.status >= 200 && res.status < 300 };
}

/**
 * POST /api/v2/owner/{assetId}/{fromTeam}/redirect — redirect asset between teams.
 */
async function redirectAsset(assetId, fromTeam, toTeam, updateToken) {
    const params = new URLSearchParams({ update_token: updateToken });
    const res = await cardPost(
        `/api/v2/owner/${encodeURIComponent(assetId)}/${encodeURIComponent(fromTeam)}/redirect?${params.toString()}`,
        { name: toTeam }
    );
    return { status: res.status, body: res.body, ok: res.status >= 200 && res.status < 300 };
}

module.exports = {
    isConfigured,
    missingVars,
    cardRequest,
    cardGet,
    cardPost,
    testConnection,
    getTeams,
    getTeamAssets,
    getOwner,
    confirmAsset,
    declineAsset,
    redirectAsset,
    invalidateToken,
};
