#!/usr/bin/env node

import fs from 'node:fs';
import path from 'node:path';
import readline from 'node:readline';
import { fileURLToPath } from 'node:url';
import log from './logger.js';
import { Bx } from './app.js';


// Paths

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = process.env.INIT_CWD || process.cwd();


// Utilities

async function ask(question){

    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });

    return new Promise(resolve => {
        rl.question(question, answer => {
            rl.close();
            resolve(answer.toLowerCase());
        });
    });
}

function copyDir(src, dst){

    fs.mkdirSync(dst, {recursive: true});
    let entries = fs.readdirSync(src, {withFileTypes: true});

    for(let entry of entries){
        let srcPath = path.join(src, entry.name);
        let dstPath = path.join(dst, entry.name);

        if(entry.isDirectory()){
            copyDir(srcPath, dstPath);
        }
		else{
            fs.copyFileSync(srcPath, dstPath);
        }
    }
}

function readPackageJson(){

	const pkgPath = path.join(projectRoot, 'package.json');
	if(!fs.existsSync(pkgPath)){ 
		return null;
	}

	try{
		const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
		if(!pkg) return null;
		return pkg;
	}
	catch(e){
		// Only error if the file is corrupted
		console.error('[bx] Could not read package.json: ', e.message);
		return null;
	}
}

function writePackageJson(obj){

	try{
		const pkgPath = path.join(projectRoot, 'package.json');
		fs.writeFileSync(pkgPath, JSON.stringify(obj, null, 2));
	}
	catch(e){
		console.error('[bx] Could not write to package.json: ', e.message);
	}
}

// Load Examples

function getExampleDir(type){
    
	const types = {
		'bootstrap': "bootstrap",
		'static': "example-1-static",
		'dynamic': "example-2-dynamic",
		'api': "example-3-api",
		'spa': "example-4-spa",
	}

    if(types.hasOwnProperty(type)){
		const exampleDir = path.join(__dirname, '..', 'examples', types[type]);
		if(!fs.existsSync(exampleDir)){
			throw new Error(`example directory ${exampleDir} not found`);
		}

		return exampleDir;
	}
	else{
		
		return getExampleDir("bootstrap");
	}
}

async function loadExample(type="bootstrap", dest, skipValidation=false){

    try{
		if(!skipValidation && !valInit()){
			msgInit();
			return;
		}

		const pkg = readPackageJson();
		const cfg = pkg.baxxon;
		const exampleDir = getExampleDir(type);
		let targetDirName = dest || cfg.vhosts.defaultDir || '_default';
		const targetDir = path.join(projectRoot, cfg.vhosts.dir, targetDirName);

		if(!skipValidation && fs.existsSync(targetDir)){
			console.warn(`[bx] Directory [${targetDirName}] already exists`);
			const answer = await ask('  Do you want to overwrite it? (y/n): ');
			if(answer !== 'y' && answer !== 'yes'){
				console.log('[bx] aborted');
				return;
			}
		}

        console.log(`[bx] Loading example [${type}] into [${targetDirName}] ...`);
        copyDir(exampleDir, targetDir);
		msgSuccess();
    } 
    catch(e){
        console.error('[bx] Error loading example:', e.message);
    }
}


// Add Vhosts

async function add(domain, dir){

	if(!dir){
		dir = domain;
	}
	
	try{
		if(!valInit()){
			msgInit();
			return;
		}

		const pkg = readPackageJson();
		console.log(`[bx] Adding vhost [${domain}] to project in ${pkg.baxxon.vhosts.dir} directory as [${dir}]`);

		// 1. Update mapping
		pkg.baxxon.vhosts.domainDirs[domain] = dir;
		writePackageJson(pkg);
		// 2. Scaffold directory
		const targetDir = path.join(projectRoot, pkg.baxxon.vhosts.dir, dir);
		if(fs.existsSync(targetDir)){
			console.log(`[bx] Directory [${pkg.baxxon.vhosts.dir}/${dir}] already exists.`);
			const answer = await ask('  Do you want to overwrite it? (y/n): ');
			if(answer !== 'y' && answer !== 'yes'){
				console.log('[bx] aborted');
				return;
			}

		}
		fs.mkdirSync(targetDir, {recursive: true});
        console.log(`[bx] Loading example [bootstrap] into [${dir}] ...`);
		const exampleDir = getExampleDir("bootstrap");
        copyDir(exampleDir, targetDir);
		msgSuccess();
	}
	catch(e){
		console.error('[bx] Failed to add vhost to project:', e.message);
	}
}


// Initialize project

async function initPackageJson(pkg){
	
	try{
		// Initialize package.json	
		const projName = projectRoot.substring(projectRoot.lastIndexOf('/') + 1) || "baxxon-app";
		const pkgDefaults = {
			"name": projName,
			"version": "1.0.0",
			"description": "",
			"keywords": [],
			"author": "",
			"license": "MIT",
		}
		
		// Merge Package defaults (Safely handling null/undefined pkg)
		let newPackageJson = {...pkgDefaults, ...(pkg || {})}
		// Set Type to module
		newPackageJson.type = "module";
		// Add start script
		newPackageJson.scripts = {
			...(pkg.scripts || {}),
			"baxxon": "node node_modules/baxxon/src/server.js",
		};
		// set Baxxon defaults
		const { errorTemplate, ...cleanDefaults } = Bx.defaultOp;
		newPackageJson.baxxon = cleanDefaults;
		// write
		writePackageJson(newPackageJson);
		console.log('[bx] Updated [package.json] with default Baxxon configuration');

		return newPackageJson.baxxon;
    }
	catch(e){
		console.error('[bx] Failed to initialize package.json: ', e.message);
    }
}

function getPaths(cfg){
	
	const paths = {
		"vhosts": path.join(projectRoot, cfg.vhosts.dir)
	}
	paths["vhostDefault"] = path.join(paths.vhosts, cfg.vhosts.defaultDir);

	return paths;
}

async function initDirs(paths){

	try{
		if(!fs.existsSync(paths.vhosts)){
			fs.mkdirSync(paths.vhosts, {recursive: true});
		}
		if(!fs.existsSync(paths.vhostDefault)){
			fs.mkdirSync(paths.vhostDefault, {recursive: true});
		}
	}
	catch(e){
		console.error("[bx] Failed to initialize directories: ", e.message);
	}
}

async function init(){

    console.log('[bx] Initializing Baxxon project...');

	try{
		// If package.json contains baxxon config then confirm before overwrite
		const pkg = readPackageJson();
		if(pkg && pkg.baxxon){
			// ask: Do you want to reinit package.json?
			console.warn("[bx] Your project has previously been initialized");
			const answer = await ask('  Do you want to overwrite it? (y/n): ');
			if(answer !== 'y' && answer !== 'yes'){
				// NO then abort
				console.log('[bx] aborted');
				return;
			}
			// YES then proceed
		}
		const cfg = await initPackageJson(pkg);
		const paths = getPaths(cfg);
		await initDirs(paths);
		await loadExample("bootstrap", cfg.vhosts.defaultDir, true);
    }
	catch(e){
		console.error('[bx] Failed to initialize: ', e.message);
	}
}

function valInit(){

	const pkg = readPackageJson();
	if(!pkg || !pkg.baxxon){

		return false;
	}
	const cfg = pkg.baxxon;
	const paths = getPaths(cfg);
	if(!fs.existsSync(paths.vhosts)){
		
		return false;
	}

	return true;
}


// CLI Router

const args = process.argv.slice(2);
const cmd = args[0];
//const isPostInstall = process.env.npm_lifecycle_event === 'postinstall';

function usage(){

    console.log('[bx] Baxxon CLI Usage:');
    console.log('  npx baxxon init                - Initialize your Baxxon project');
    console.log('  npx baxxon add <domain> [dir]  - Add vhost mapping to your project');
    console.log('  npx baxxon load <type> [dest]  - Load an example into sites/[dest]');
    console.log('[bx] also https://baxxon.ca/docs');
}

function msgSuccess(){

	console.log('[bx] To start your server:');
	console.log('  npm run baxxon\n');
}

function msgInit(){
	
	console.warn('[bx] To initialize your Baxxon project:');
	console.log('  npx baxxon init\n');
}

if(cmd === 'load'){
	(!args[1]) ? usage() : loadExample(args[1], args[2]);
}
else if(cmd === 'add'){
	(!args[1]) ? usage() : add(args[1], args[2]);
}
else if(cmd === 'init'){
    init();
}
else{
	usage();
}
