#!/usr/bin/env node
/**
 * Extract SSL/TLS certificate details from a URL.
 *
 * This hook sets up CDP listeners BEFORE chrome_navigate loads the page,
 * then waits for navigation to complete. The listener captures SSL details
 * during the navigation request.
 *
 * Usage: on_Snapshot__23_ssl.js --url=<url> --snapshot-id=<uuid>
 * Output: Writes ssl.jsonl
 */

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

// Add NODE_MODULES_DIR to module resolution paths if set
if (process.env.NODE_MODULES_DIR) module.paths.unshift(process.env.NODE_MODULES_DIR);

const puppeteer = require('puppeteer-core');

// Import shared utilities from chrome_utils.js
const {
    getEnvBool,
    getEnvInt,
    parseArgs,
    connectToPage,
    waitForPageLoaded,
} = require('../chrome/chrome_utils.js');

const PLUGIN_NAME = 'ssl';
const OUTPUT_DIR = '.';
const OUTPUT_FILE = 'ssl.jsonl';
const CHROME_SESSION_DIR = '../chrome';

let browser = null;
let page = null;
let client = null;
let sslCaptured = false;
let shuttingDown = false;

async function setupListener(url) {
    const outputPath = path.join(OUTPUT_DIR, OUTPUT_FILE);
    const timeout = getEnvInt('SSL_TIMEOUT', 30) * 1000;
    let targetHost = null;

    // Only extract SSL for HTTPS URLs
    if (!url.startsWith('https://')) {
        throw new Error('URL is not HTTPS');
    }

    try {
        targetHost = new URL(url).host;
    } catch (e) {
        targetHost = null;
    }

    // Connect to Chrome page using shared utility
    const { browser, page } = await connectToPage({
        chromeSessionDir: CHROME_SESSION_DIR,
        timeoutMs: timeout,
        puppeteer,
    });

    client = await page.target().createCDPSession();
    await client.send('Network.enable');

    client.on('Network.responseReceived', (params) => {
        try {
            if (sslCaptured) return;
            if (params.type && params.type !== 'Document') return;
            const response = params.response || {};
            const responseUrl = response.url || '';
            if (!responseUrl.startsWith('http')) return;

            if (targetHost) {
                try {
                    const responseHost = new URL(responseUrl).host;
                    if (responseHost !== targetHost) return;
                } catch (e) {
                    // Ignore URL parse errors, fall through
                }
            }

            const securityDetails = response.securityDetails || null;
            let sslInfo = { url: responseUrl };

            if (securityDetails) {
                sslInfo.protocol = securityDetails.protocol;
                sslInfo.subjectName = securityDetails.subjectName;
                sslInfo.issuer = securityDetails.issuer;
                sslInfo.validFrom = securityDetails.validFrom;
                sslInfo.validTo = securityDetails.validTo;
                sslInfo.certificateId = securityDetails.subjectName;
                sslInfo.securityState = response.securityState || 'secure';
                sslInfo.schemeIsCryptographic = true;

                const sanList = securityDetails.sanList;
                if (sanList && sanList.length > 0) {
                    sslInfo.subjectAlternativeNames = sanList;
                }
            } else if (responseUrl.startsWith('https://')) {
                sslInfo.securityState = response.securityState || 'unknown';
                sslInfo.schemeIsCryptographic = true;
                sslInfo.error = 'No security details available';
            } else {
                sslInfo.securityState = 'insecure';
                sslInfo.schemeIsCryptographic = false;
            }

            fs.writeFileSync(outputPath, JSON.stringify(sslInfo, null, 2));
            sslCaptured = true;
        } catch (e) {
            // Ignore errors
        }
    });

    return { browser, page };
}

function emitResult(status = 'succeeded') {
    if (shuttingDown) return;
    shuttingDown = true;

    const outputStr = sslCaptured ? OUTPUT_FILE : OUTPUT_FILE;
    console.log(JSON.stringify({
        type: 'ArchiveResult',
        status,
        output_str: outputStr,
    }));
}

async function handleShutdown(signal) {
    console.error(`\nReceived ${signal}, emitting final results...`);
    emitResult('succeeded');
    if (browser) {
        try {
            browser.disconnect();
        } catch (e) {}
    }
    process.exit(0);
}

async function main() {
    const args = parseArgs();
    const url = args.url;
    const snapshotId = args.snapshot_id;

    if (!url || !snapshotId) {
        console.error('Usage: on_Snapshot__23_ssl.js --url=<url> --snapshot-id=<uuid>');
        process.exit(1);
    }

    if (!getEnvBool('SSL_ENABLED', true)) {
        console.error('Skipping (SSL_ENABLED=False)');
        console.log(JSON.stringify({type: 'ArchiveResult', status: 'skipped', output_str: 'SSL_ENABLED=False'}));
        process.exit(0);
    }

    try {
        // Set up listener BEFORE navigation
        const connection = await setupListener(url);
        browser = connection.browser;
        page = connection.page;

        // Register signal handlers for graceful shutdown
        process.on('SIGTERM', () => handleShutdown('SIGTERM'));
        process.on('SIGINT', () => handleShutdown('SIGINT'));

        // Wait for chrome_navigate to complete (non-fatal)
        try {
            const timeout = getEnvInt('SSL_TIMEOUT', 30) * 1000;
            await waitForPageLoaded(CHROME_SESSION_DIR, timeout * 4);
        } catch (e) {
            console.error(`WARN: ${e.message}`);
        }

        // console.error('SSL listener active, waiting for cleanup signal...');
        await new Promise(() => {}); // Keep alive until SIGTERM
        return;

    } catch (e) {
        const error = `${e.name}: ${e.message}`;
        console.error(`ERROR: ${error}`);

        console.log(JSON.stringify({
            type: 'ArchiveResult',
            status: 'failed',
            output_str: error,
        }));
        process.exit(1);
    }
}

main().catch(e => {
    console.error(`Fatal error: ${e.message}`);
    process.exit(1);
});
