/**
 * Config Resolver Utility
 *
 * Mengelola resolusi path file konfigurasi (.env) untuk seluruh CLI command
 * yang menerima parameter --config. Termasuk:
 *   - Cascade lookup: cwd -> config/ folder -> +.env extension
 *   - Default config lookup (.restforge/defaults.json) sebagai fallback
 *     jika user tidak menyediakan --config eksplisit
 *
 * Default config disimpan per working directory di file:
 *   {cwd}/.restforge/defaults.json
 *
 * Format file defaults.json:
 *   {
 *     "config": "db-postgres.env",
 *     "setAt": "2026-05-13T10:30:00.000Z"
 *   }
 *
 * @module lib/utils/config-resolver
 */

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

const DEFAULTS_DIR_NAME = '.restforge';
const DEFAULTS_FILE_NAME = 'defaults.json';

/**
 * Path ke file defaults.json di working directory tertentu.
 *
 * @param {string} [cwd] - Working directory (default: process.cwd())
 * @returns {string} Absolute path ke defaults.json
 */
function getDefaultsFilePath(cwd) {
    const baseDir = cwd || process.cwd();
    return path.join(baseDir, DEFAULTS_DIR_NAME, DEFAULTS_FILE_NAME);
}

/**
 * Baca default config dari .restforge/defaults.json.
 *
 * @param {string} [cwd] - Working directory (default: process.cwd())
 * @returns {{ config: string, setAt: string } | null}
 *          Object default config atau null jika file tidak ada / invalid.
 */
function getDefaultConfig(cwd) {
    const filePath = getDefaultsFilePath(cwd);

    if (!fs.existsSync(filePath)) {
        return null;
    }

    try {
        const content = fs.readFileSync(filePath, 'utf-8');
        const data = JSON.parse(content);

        if (data && typeof data.config === 'string' && data.config.length > 0) {
            return {
                config: data.config,
                setAt: data.setAt || null
            };
        }

        return null;
    } catch (error) {
        return null;
    }
}

/**
 * Simpan default config ke .restforge/defaults.json.
 *
 * @param {string} configFileName - Nama file config yang akan dijadikan default
 * @param {string} [cwd] - Working directory (default: process.cwd())
 * @returns {{ filePath: string }} Path file defaults.json yang ditulis
 * @throws {Error} Jika gagal menulis file
 */
function setDefaultConfig(configFileName, cwd) {
    const baseDir = cwd || process.cwd();
    const dirPath = path.join(baseDir, DEFAULTS_DIR_NAME);
    const filePath = path.join(dirPath, DEFAULTS_FILE_NAME);

    if (!fs.existsSync(dirPath)) {
        fs.mkdirSync(dirPath, { recursive: true });
    }

    const data = {
        config: configFileName,
        setAt: new Date().toISOString()
    };

    fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');

    return { filePath };
}

/**
 * Hapus file default config.
 *
 * @param {string} [cwd] - Working directory (default: process.cwd())
 * @returns {{ removed: boolean, filePath: string }}
 *          removed = true jika file ada dan berhasil dihapus,
 *          removed = false jika file tidak ada (no-op).
 */
function clearDefaultConfig(cwd) {
    const filePath = getDefaultsFilePath(cwd);

    if (!fs.existsSync(filePath)) {
        return { removed: false, filePath };
    }

    fs.unlinkSync(filePath);
    return { removed: true, filePath };
}

/**
 * Resolve nama file config menjadi absolute path dengan cascade lookup.
 * Lookup order:
 *   1. Path direct di cwd (atau absolute path)
 *   2. {cwd}/config/{filename}
 *   3. {cwd}/config/{filename}.env (jika tidak berakhiran .env)
 *
 * @param {string} configFileName - Nama file atau path config
 * @param {string} [cwd] - Working directory (default: process.cwd())
 * @returns {string} Absolute path hasil resolusi
 *                   (jika tidak ditemukan, return absolute path dari cwd join)
 */
function resolveConfigFilePath(configFileName, cwd) {
    const baseDir = cwd || process.cwd();

    if (path.isAbsolute(configFileName)) {
        return configFileName;
    }

    const direct = path.resolve(baseDir, configFileName);
    if (fs.existsSync(direct)) {
        return direct;
    }

    const hasSeparator = configFileName.includes('/') || configFileName.includes('\\');
    if (!hasSeparator) {
        const inConfigFolder = path.resolve(baseDir, 'config', configFileName);
        if (fs.existsSync(inConfigFolder)) {
            return inConfigFolder;
        }

        if (!configFileName.endsWith('.env')) {
            const withExt = configFileName + '.env';
            const inConfigFolderExt = path.resolve(baseDir, 'config', withExt);
            if (fs.existsSync(inConfigFolderExt)) {
                return inConfigFolderExt;
            }
            const directExt = path.resolve(baseDir, withExt);
            if (fs.existsSync(directExt)) {
                return directExt;
            }
        }
    }

    return direct;
}

/**
 * Resolve config dengan dukungan default fallback.
 *
 * Logika:
 *   1. Jika configArg disediakan -> resolve dengan cascade lookup
 *   2. Jika configArg tidak disediakan -> baca default dari .restforge/defaults.json
 *      - Jika default ada -> resolve dengan cascade lookup, source='default'
 *      - Jika tidak ada -> return null (caller harus tampilkan error mandatory)
 *
 * @param {string|null|undefined} configArg - Nilai --config dari argumen CLI
 * @param {string} [cwd] - Working directory (default: process.cwd())
 * @returns {{
 *   path: string,
 *   source: 'argument' | 'default',
 *   defaultName: string | null
 * } | null}
 *   - path: Absolute path hasil resolusi
 *   - source: 'argument' jika dari --config, 'default' jika dari defaults.json
 *   - defaultName: Nama default config (hanya terisi jika source='default')
 *   - null: Jika configArg kosong dan tidak ada default
 */
function resolveConfig(configArg, cwd) {
    if (configArg) {
        return {
            path: resolveConfigFilePath(configArg, cwd),
            source: 'argument',
            defaultName: null
        };
    }

    const def = getDefaultConfig(cwd);
    if (def) {
        return {
            path: resolveConfigFilePath(def.config, cwd),
            source: 'default',
            defaultName: def.config
        };
    }

    return null;
}

/**
 * Tampilkan warning di stderr saat config di-resolve dari default config.
 * Helper agar pesan konsisten lintas command.
 *
 * @param {string} defaultName - Nama default config (dari resolveConfig().defaultName)
 */
function printDefaultConfigWarning(defaultName) {
    process.stderr.write(
        `[restforge] Using default config: ${defaultName} ` +
        `(run 'restforge config get-default' to verify)\n`
    );
}

module.exports = {
    getDefaultsFilePath,
    getDefaultConfig,
    setDefaultConfig,
    clearDefaultConfig,
    resolveConfigFilePath,
    resolveConfig,
    printDefaultConfigWarning
};
