'use strict';

/**
 * Endpoint Schema Validator
 *
 * Orchestrator untuk validasi schema database saat `endpoint create` dijalankan.
 * Module ini:
 *   1. Resolve --config file (via config-resolver)
 *   2. Load config + connect ke database (via DatabaseIntrospector)
 *   3. Compare payload-vs-database menggunakan compareSchemaStrict
 *   4. Map hasil ke exit-code semantics:
 *        - clean schema -> return { status: 'ok' }
 *        - drift detected -> throw error dengan exitCode=1 + formatted report
 *        - connection fail -> throw error dengan exitCode=3
 *        - usage error -> throw error dengan exitCode=2 (di-handle caller via validateContract)
 *
 * Module ini sengaja decoupled dari PayloadGenerator agar tidak ikut menjalankan
 * payload regenerate/sync flow.
 *
 * @module lib/payload/endpoint-schema-validator
 */

const path = require('path');
const { DatabaseIntrospector } = require('../utils/database-introspector');
const { resolveConfig, printDefaultConfigWarning } = require('../utils/config-resolver');
const { compareSchemaStrict, formatDriftReport } = require('./schema-diff');

/**
 * Buat Error object dengan property `exitCode` agar cli-entry dapat
 * forward exit code spesifik.
 *
 * @param {string} message
 * @param {number} exitCode
 * @returns {Error}
 */
function createExitError(message, exitCode) {
    const err = new Error(message);
    err.exitCode = exitCode;
    err.isSchemaValidationError = true;
    return err;
}

/**
 * Jalankan validasi schema untuk satu payload + table sebelum codegen.
 *
 * @param {Object} options
 * @param {Object} options.payload - Processed payload object (harus punya tableName)
 * @param {string} options.payloadFileName - Nama file payload untuk pesan error
 * @param {string} [options.payloadFilePath] - Absolute path ke payload file, untuk
 *   resolve `file:` reference di viewQuery/datatablesQuery/exportQuery. Jika tidak
 *   di-set, JOIN field validation tetap jalan untuk inline SQL tapi `file:` reference
 *   akan masuk warning.
 * @param {string} options.configArg - Nilai flag --config dari CLI
 * @param {boolean} options.skipSchemaCheck - true bila --skip-schema-check di-set
 * @param {string} [options.workingDir] - cwd, default process.cwd()
 * @param {Function} [options.IntrospectorClass] - Override untuk testing
 * @returns {Promise<{
 *   status: 'ok' | 'skipped',
 *   reason?: string,
 *   columnsChecked?: number
 * }>}
 * @throws {Error} Dengan property exitCode (1=drift, 2=usage, 3=connection)
 */
async function validateEndpointSchema(options) {
    const {
        payload,
        payloadFileName,
        payloadFilePath,
        configArg,
        skipSchemaCheck,
        workingDir,
        IntrospectorClass
    } = options;

    if (!payload || !payload.tableName) {
        throw createExitError('Payload missing tableName — cannot run schema validation', 2);
    }

    if (skipSchemaCheck) {
        return { status: 'skipped', reason: '--skip-schema-check active' };
    }

    const resolved = resolveConfig(configArg, workingDir || process.cwd());
    if (!resolved) {
        const lines = [
            'Error: --config is required for schema validation',
            'Usage: npx restforge endpoint create --project=<P> --name=<N> --payload=<F> --config=<ENV> [--force=true] [--skip-schema-check]'
        ];
        throw createExitError(lines.join('\n'), 2);
    }

    if (resolved.source === 'default') {
        printDefaultConfigWarning(resolved.defaultName);
    }

    const IntroCtor = IntrospectorClass || DatabaseIntrospector;
    const db = new IntroCtor({ quiet: true });

    const loaded = db.loadConfig(resolved.path);
    if (!loaded) {
        throw createExitError(
            buildConnectionErrorMessage(`Cannot load config file: ${resolved.path}`),
            3
        );
    }

    let connected = false;
    try {
        connected = await db.connect();
    } catch (e) {
        try { await db.close(); } catch (_e) { /* ignore */ }
        throw createExitError(
            buildConnectionErrorMessage(e.message || String(e)),
            3
        );
    }

    if (!connected) {
        try { await db.close(); } catch (_e) { /* ignore */ }
        throw createExitError(
            buildConnectionErrorMessage('database not reachable'),
            3
        );
    }

    const payloadDir = payloadFilePath
        ? path.dirname(payloadFilePath)
        : path.join(workingDir || process.cwd(), 'payload');

    let comparison;
    try {
        comparison = await compareSchemaStrict(payload, db, { payloadDir });
    } finally {
        try { await db.close(); } catch (_e) { /* ignore */ }
    }

    if (comparison.status === 'error') {
        const lines = [
            'Schema Validation:',
            `  [ERROR] ${comparison.summary}`,
            '  Endpoint generation blocked.',
            '',
            '  Workarounds:',
            '    1. Verify the table exists in the database',
            '    2. Use --skip-schema-check to bypass validation (NOT recommended for production)'
        ];
        throw createExitError(lines.join('\n'), 3);
    }

    if (comparison.status === 'drift') {
        const driftLines = formatDriftReport(comparison, {
            payloadFileName,
            tableName: payload.tableName
        });
        const lines = ['Schema Validation:', ...driftLines, '', 'Endpoint generation blocked due to schema drift'];
        throw createExitError(lines.join('\n'), 1);
    }

    return {
        status: 'ok',
        columnsChecked: comparison.totalColumnsChecked || 0
    };
}

function buildConnectionErrorMessage(reason) {
    const lines = [
        'Schema Validation:',
        `  [ERROR] Cannot connect to database: ${reason}`,
        '  Endpoint generation blocked.',
        '',
        '  Workarounds:',
        '    1. Verify db-connection.env is correct and database is reachable',
        '    2. Use --skip-schema-check to bypass validation (NOT recommended for production)'
    ];
    return lines.join('\n');
}

module.exports = {
    validateEndpointSchema,
    createExitError
};
