#!/usr/bin/env tsx
import { parseArgs } from 'node:util';

import { DockerImageNotFoundError } from './docker-image-not-found-error';
import type { N8NConfig, N8NStack } from './n8n-test-container-creation';
import { createN8NStack } from './n8n-test-container-creation';
import { BASE_PERFORMANCE_PLANS, isValidPerformancePlan } from './performance-plans';

// ANSI colors for terminal output
const colors = {
	reset: '\x1b[0m',
	bright: '\x1b[1m',
	green: '\x1b[32m',
	yellow: '\x1b[33m',
	blue: '\x1b[34m',
	red: '\x1b[31m',
	cyan: '\x1b[36m',
};

const log = {
	info: (msg: string) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`),
	success: (msg: string) => console.log(`${colors.green}✓${colors.reset} ${msg}`),
	error: (msg: string) => console.error(`${colors.red}✗${colors.reset} ${msg}`),
	warn: (msg: string) => console.warn(`${colors.yellow}⚠${colors.reset} ${msg}`),
	header: (msg: string) => console.log(`\n${colors.bright}${colors.cyan}${msg}${colors.reset}\n`),
};

function showHelp() {
	console.log(`
${colors.bright}n8n Stack Manager${colors.reset}

Start n8n containers for development and testing.

${colors.yellow}Usage:${colors.reset}
  npm run stack [options]

${colors.yellow}Options:${colors.reset}
  --postgres        Use PostgreSQL instead of SQLite
  --queue           Enable queue mode (requires PostgreSQL)
  --task-runner     Enable external task runner container
  --mains <n>       Number of main instances (default: 1)
  --workers <n>     Number of worker instances (default: 1)
  --name <name>     Project name for parallel runs
  --env KEY=VALUE   Set environment variables
  --plan <plan>     Use performance plan preset (${Object.keys(BASE_PERFORMANCE_PLANS).join(', ')})
  --help, -h        Show this help

${colors.yellow}Performance Plans:${colors.reset}
${Object.entries(BASE_PERFORMANCE_PLANS)
	.map(
		([name, plan]) =>
			`  ${name.padEnd(12)} ${plan.memory}GB RAM, ${plan.cpu} CPU cores - SQLite only`,
	)
	.join('\n')}

${colors.yellow}Environment Variables:${colors.reset}
  • N8N_DOCKER_IMAGE=<image>  Use a custom Docker image (default: n8nio/n8n:local)

${colors.yellow}Examples:${colors.reset}
  ${colors.bright}# Simple SQLite instance${colors.reset}
  npm run stack

  ${colors.bright}# PostgreSQL database${colors.reset}
  npm run stack --postgres

  ${colors.bright}# Queue mode (automatically uses PostgreSQL)${colors.reset}
  npm run stack --queue

  ${colors.bright}# With external task runner${colors.reset}
  npm run stack --postgres --task-runner

  ${colors.bright}# Custom scaling${colors.reset}
  npm run stack --queue --mains 3 --workers 5

  ${colors.bright}# With environment variables${colors.reset}
  npm run stack --postgres --env N8N_LOG_LEVEL=info --env N8N_ENABLED_MODULES=insights

  ${colors.bright}# Performance plan presets${colors.reset}
${Object.keys(BASE_PERFORMANCE_PLANS)
	.map((name) => `  npm run stack --plan ${name}`)
	.join('\n')}

  ${colors.bright}# Parallel instances${colors.reset}
  npm run stack --name test-1
  npm run stack --name test-2

${colors.yellow}Notes:${colors.reset}
  • SQLite is the default database (no external dependencies)
  • Queue mode requires PostgreSQL and enables horizontal scaling
  • Use --name for running multiple instances in parallel
  • Performance plans simulate cloud constraints (SQLite only, resource-limited)
  • Press Ctrl+C to stop all containers
`);
}

async function main() {
	const { values } = parseArgs({
		args: process.argv.slice(2),
		options: {
			help: { type: 'boolean', short: 'h' },
			postgres: { type: 'boolean' },
			queue: { type: 'boolean' },
			'task-runner': { type: 'boolean' },
			mains: { type: 'string' },
			workers: { type: 'string' },
			name: { type: 'string' },
			env: { type: 'string', multiple: true },
			plan: { type: 'string' },
		},
		allowPositionals: false,
	});

	// Show help if requested
	if (values.help) {
		showHelp();
		process.exit(0);
	}

	// Build configuration
	const config: N8NConfig = {
		postgres: values.postgres ?? false,
		taskRunner: values['task-runner'] ?? false,
		projectName: values.name ?? `n8n-stack-${Math.random().toString(36).substring(7)}`,
	};

	// Handle queue mode
	if (values.queue ?? values.mains ?? values.workers) {
		const mains = parseInt(values.mains ?? '1', 10);
		const workers = parseInt(values.workers ?? '1', 10);

		if (isNaN(mains) || isNaN(workers) || mains < 1 || workers < 0) {
			log.error('Invalid mains or workers count');
			process.exit(1);
		}

		config.queueMode = { mains, workers };

		if (!values.queue && (values.mains ?? values.workers)) {
			log.warn('--mains and --workers imply queue mode');
		}
	}

	if (values.plan) {
		const planName = values.plan;
		if (!isValidPerformancePlan(planName)) {
			log.error(`Invalid performance plan: ${values.plan}`);
			log.error(`Available plans: ${Object.keys(BASE_PERFORMANCE_PLANS).join(', ')}`);
			process.exit(1);
		}

		const plan = BASE_PERFORMANCE_PLANS[planName];

		if (values.postgres) {
			log.warn('Performance plans use SQLite only. PostgreSQL option ignored.');
		}
		if (values.queue || values.mains || values.workers) {
			log.warn('Performance plans use SQLite only. Queue mode ignored.');
		}

		config.resourceQuota = plan;
		config.postgres = false; // Force SQLite for performance plans
		config.queueMode = false; // Force single instance for performance plans

		log.info(
			`Using ${planName} performance plan: ${plan.memory}GB RAM, ${plan.cpu} CPU cores (SQLite only)`,
		);
	}

	// Parse environment variables
	if (values.env && values.env.length > 0) {
		config.env = {};

		for (const envStr of values.env) {
			const [key, ...valueParts] = envStr.split('=');
			const value = valueParts.join('='); // Handle values with = in them

			if (key && value) {
				config.env[key] = value;
			} else {
				log.warn(`Invalid env format: ${envStr} (expected KEY=VALUE)`);
			}
		}
	}

	log.header('Starting n8n Stack');
	log.info(`Project name: ${config.projectName}`);
	displayConfig(config);

	let stack: N8NStack;

	try {
		log.info('Starting containers...');
		try {
			stack = await createN8NStack(config);
		} catch (error) {
			if (error instanceof DockerImageNotFoundError) {
				log.error(error.message);
				process.exit(1);
			}
			throw error;
		}

		log.success('All containers started successfully!');
		console.log('');
		log.info(`n8n URL: ${colors.bright}${colors.green}${stack.baseUrl}${colors.reset}`);
	} catch (error) {
		log.error(`Failed to start: ${error as string}`);
		process.exit(1);
	}
}

function displayConfig(config: N8NConfig) {
	const dockerImage = process.env.N8N_DOCKER_IMAGE ?? 'n8nio/n8n:local';
	log.info(`Docker image: ${dockerImage}`);

	// Determine actual database
	// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
	const usePostgres = config.postgres || config.queueMode;
	log.info(`Database: ${usePostgres ? 'PostgreSQL' : 'SQLite'}`);

	if (config.queueMode) {
		const qm = typeof config.queueMode === 'boolean' ? { mains: 1, workers: 1 } : config.queueMode;
		log.info(`Queue mode: ${qm.mains} main(s), ${qm.workers} worker(s)`);
		if (!config.postgres) {
			log.info('(PostgreSQL automatically enabled for queue mode)');
		}
		if (qm.mains && qm.mains > 1) {
			log.info('(load balancer will be configured)');
		}
	} else {
		log.info('Queue mode: disabled');
	}

	// Display task runner status
	if (config.taskRunner) {
		log.info('Task runner: enabled (external container)');
		if (!usePostgres) {
			log.warn('Task runner recommended with PostgreSQL for better performance');
		}
	} else {
		log.info('Task runner: disabled');
	}

	if (config.resourceQuota) {
		log.info(
			`Resource limits: ${config.resourceQuota.memory}GB RAM, ${config.resourceQuota.cpu} CPU cores`,
		);
	}

	if (config.env) {
		const envCount = Object.keys(config.env).length;
		if (envCount > 0) {
			log.info(`Environment variables: ${envCount} custom variable(s)`);
			Object.entries(config.env).forEach(([key, value]) => {
				console.log(`  ${key}=${value}`);
			});
		}
	}

	if (process.env.TESTCONTAINERS_REUSE_ENABLE === 'true') {
		log.info('Container reuse: enabled (containers will persist)');
	}
}

// Run if executed directly
if (require.main === module) {
	main().catch((error) => {
		log.error(`Unexpected error: ${error}`);
		process.exit(1);
	});
}
