const { resolve } = require('path');
const { writeFileSync, existsSync, readFileSync, mkdirSync, readdirSync } = require('fs');
const Mustache = require('mustache');

const baseDir = resolve(__dirname, '../../../src');
const modelsDir = resolve(baseDir, 'models');

/**
 * Returns list of model names (PascalCase) found in src/models/
 * Avoids importing compiled dist which may not exist.
 */
function getModelNames() {
	return readdirSync(modelsDir)
		.filter((f) => f.endsWith('.ts') && !['index.ts', 'init-models.ts'].includes(f))
		.map((f) => f.replace('.ts', ''));
}

/**
 * Parses attribute names from the TypeScript interface in a model file.
 * Falls back to empty array if the file cannot be parsed.
 */
function getModelAttributes(modelUpperCase) {
	const modelFilePath = resolve(modelsDir, `${modelUpperCase}.ts`);
	if (!existsSync(modelFilePath)) return [];
	const content = readFileSync(modelFilePath, 'utf-8');
	const interfaceMatch = content.match(/export interface \w+Attributes \{([\s\S]*?)\}/);
	if (!interfaceMatch) return [];
	const attributes = [];
	for (const line of interfaceMatch[1].split('\n')) {
		const match = line.match(/^\s+(\w+)\??\s*:/);
		if (match) attributes.push(match[1]);
	}
	return attributes;
}

/**
 * Simple English pluralization for model names.
 * Handles common cases: y→ies, s/x/z/ch/sh→es, default→s
 */
function pluralize(word) {
	if (/[^aeiou]y$/i.test(word)) return word.slice(0, -1) + 'ies';
	if (/(s|x|z|ch|sh)$/i.test(word)) return word + 'es';
	return word + 's';
}

/**
 * Converts PascalCase to readable text with spaces (e.g., ProductCategory -> Product Categories)
 */
function toReadableName(str) {
	return str
		.replace(/([A-Z])/g, ' $1')
		.trim()
		.replace(/^./, (char) => char.toUpperCase());
}

/**
 * Converts PascalCase or camelCase to kebab-case.
 * ProductCategory → product-category
 */
function toKebabCase(str) {
	return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}

const controllerPath = resolve(baseDir, 'controllers/api/v1');
const providerPath = resolve(baseDir, 'providers');
const contractsPath = resolve(baseDir, 'contracts');

const execute = async () => {
	try {
		const args = process.argv.slice(2);

		// Check for mode flag
		let mode = 'crud'; // default to crud
		let argIndex = 0;

		if (args[0] === '--bulk') {
			mode = 'bulk';
			argIndex = 1;
		} else if (args[0] === '--crud') {
			mode = 'crud';
			argIndex = 1;
		}

		const remainingArgs = args.slice(argIndex);

		if (remainingArgs.length < 1) {
			console.log('Usage: node index.js [--crud|--bulk] <model> [domain] [apiPath] [swaggerTag]');
			console.log('Examples:');
			console.log('  node index.js user                    # Generate CRUD APIs');
			console.log('  node index.js --bulk user            # Generate bulk APIs');
			console.log('  node index.js --crud user user-management users UserManagement');
			console.log('Note: --dry-run mode has been removed. Generation now happens directly.');
			return;
		}

		const modelRaw = remainingArgs[0]?.trim();
		// Normalize: PascalCase for model lookup, lowercase for file/folder paths
		const modelUpperCaseRaw = modelRaw.replace(/^./, modelRaw.charAt(0).toUpperCase());
		const modelLowerCase = modelRaw.charAt(0).toLowerCase() + modelRaw.slice(1);
		const modelKebabCase = toKebabCase(modelUpperCaseRaw);
		const modelReadableName = toReadableName(modelUpperCaseRaw);
		const modelReadableNamePlural = toReadableName(pluralize(modelUpperCaseRaw));
		const modelReadableNameLower = modelReadableName.toLowerCase();
		const modelReadableNamePluralLower = modelReadableNamePlural.toLowerCase();
		const modelCamelCasePlural = pluralize(modelLowerCase).replace(/^./, (char) => char.toUpperCase());
		const model = modelLowerCase;
		let domain = remainingArgs[1]?.trim() || '';
		const apiPathLink = remainingArgs[2]?.trim() || pluralize(modelKebabCase);
		const swaggerTag = remainingArgs[3]?.trim() || modelReadableNamePlural;

		// Skip --dry-run as domain
		if (domain === '--dry-run') {
			domain = '';
		}

		if (!model) {
			console.log('Model name cannot be empty.');
			return;
		}

		const modelNames = getModelNames();
		const modelUpperCase = modelUpperCaseRaw;
		if (!modelNames.includes(modelUpperCase)) {
			console.log(`No model match your input. Available models: ${modelNames.join(', ')}`);
			return;
		}

		// Get model attributes for conditional template rendering
		const modelAttributes = getModelAttributes(modelUpperCase);

		const SYSTEM_FIELDS = new Set([
			'id',
			'created_at',
			'updated_at',
			'deleted_at',
			'deleted_by',
			'created_by',
			'updated_by',
		]);
		const mutateFields = modelAttributes
			.filter((attr) => !SYSTEM_FIELDS.has(attr))
			.map((attr) => ({ name: attr, isLast: false }))
			.map((f, i, arr) => ({ ...f, isLast: i === arr.length - 1 }));

		const hasStatus = modelAttributes.includes('status');
		const hasIsActive = modelAttributes.includes('isActive') || modelAttributes.includes('is_active');
		const hasIsDeleted =
			modelAttributes.includes('isDeleted') ||
			modelAttributes.includes('is_deleted') ||
			modelAttributes.includes('deleted_at');

		const domainPath = domain ? `${domain}/` : '';
		const providerBasePath = resolve(providerPath, domain);
		const contractsBasePath = resolve(contractsPath, domain);

		// Prepare replacements
		const replace = {
			modelUpperCase,
			modelLowerCase,
			modelKebabCase,
			modelReadableName,
			modelReadableNamePlural,
			modelReadableNameLower,
			modelReadableNamePluralLower,
			modelCamelCasePlural,
			swaggerTag,
			apiPathLink,
			domainPath,
			mutateFields,
			isBulk: mode === 'bulk',
			isCrud: mode === 'crud',
			hasStatus,
			hasIsActive,
			hasIsDeleted,
		};

		// Read templates
		const providerFile = readFileSync(`${__dirname}/templates/provider.mustache`, 'utf-8');
		const controllerAllFile = readFileSync(`${__dirname}/templates/controllerAllPath.mustache`, 'utf-8');
		const controllerIdFile = readFileSync(`${__dirname}/templates/controllerIdPath.mustache`, 'utf-8');
		const schemaFile = readFileSync(`${__dirname}/templates/schema.mustache`, 'utf-8');
		const pathsFile = readFileSync(`${__dirname}/templates/paths.mustache`, 'utf-8');

		const apiPath = resolve(controllerPath, apiPathLink);
		const providerFilePath = resolve(providerBasePath, `${modelUpperCase}Provider.ts`);
		const schemaFilePath = resolve(contractsBasePath, modelKebabCase, 'schema.ts');
		const pathsFilePath = resolve(contractsBasePath, modelKebabCase, 'paths.ts');

		// Generate provider if not exists
		if (!existsSync(providerFilePath)) {
			mkdirSync(providerBasePath, { recursive: true });
			writeFileSync(providerFilePath, Mustache.render(providerFile, replace));
			console.log(`✓ Generated provider: ${providerFilePath}`);
		} else {
			console.log(`Provider already exists: ${providerFilePath}`);
		}

		// Generate Zod schema (append if exists, create new if not)
		const schemaDir = resolve(contractsBasePath, modelKebabCase);
		if (existsSync(schemaFilePath)) {
			// Append to existing schema file
			const existingContent = readFileSync(schemaFilePath, 'utf-8');
			const newSchemaContent = Mustache.render(schemaFile, replace);
			// Check if the schema already exists
			if (!existingContent.includes(`${modelUpperCase}CreateBodySchema`)) {
				writeFileSync(schemaFilePath, existingContent + '\n' + newSchemaContent);
				console.log(`✓ Appended Zod schemas to: ${schemaFilePath}`);
			} else {
				console.log(`Zod schemas already exist in: ${schemaFilePath}`);
			}
		} else {
			mkdirSync(schemaDir, { recursive: true });
			writeFileSync(schemaFilePath, Mustache.render(schemaFile, replace));
			console.log(`✓ Generated Zod schema file: ${schemaFilePath}`);
		}

		// Generate OpenAPI paths (only if not exists)
		if (!existsSync(pathsFilePath)) {
			mkdirSync(schemaDir, { recursive: true });
			writeFileSync(pathsFilePath, Mustache.render(pathsFile, replace));
			console.log(`✓ Generated OpenAPI paths file: ${pathsFilePath}`);
		} else {
			console.log(`OpenAPI paths file already exists: ${pathsFilePath}`);
		}

		// Generate controllers
		mkdirSync(apiPath, { recursive: true });

		// Generate index.ts (all path operations)
		writeFileSync(`${apiPath}/index.ts`, Mustache.render(controllerAllFile, replace));
		console.log(`✓ Generated controller: ${apiPath}/index.ts`);

		// Generate {id}.ts only for CRUD mode
		if (mode === 'crud') {
			writeFileSync(`${apiPath}/{id}.ts`, Mustache.render(controllerIdFile, replace));
			console.log(`✓ Generated controller: ${apiPath}/{id}.ts`);
		}

		console.log(`✓ API generation complete for ${modelUpperCase} (${mode} mode)`);
	} catch (error) {
		console.error('Error generating API:', error);
	}
};

execute();
