import { basename, join, resolve } from 'node:path';
import { cwd } from 'node:process';

const defaultRoute = {
    source: '.*',
    service: 'app-front'
};
const defaultVersionName = '1.0.0';
const defaultBusinessSolution = 'srv';

const getJsonFileWriter = ({fs, console}) => (name, json) => {
    const data = JSON.stringify(json, null, 4);
    console.debug(`Writing '${name}' with contents:\n${data}`);
    return fs.writeFile(name, data);
};

const cdmEntity = (id, title, type, payload) => {
    return {
        _version: '3.0',
        identification: {
            id,
            title: `{{${id}Title}}`,
            entityType: type
        },
        payload,
        texts: [{
            locale: '',
            textDictionary: {
                [`${id}Title`]: title
            }
        }]
    };
};

const getCDM = (manifests) => {
    const businessSolution = manifests[0]['sap.cloud'].service;
    const id = businessSolution.replace(/[^a-z0-9]/gm, '');
    const viz = [];
    const apps = [];
    manifests.forEach(manifest => {
        const appName = manifest['sap.app'].id;
        viz.push({ appId: appName, vizId: appName });
        apps.push({ id: appName });
    });
    return [
        cdmEntity(id + 'CatalogId', businessSolution + ' Catalog', 'catalog', { viz }),
        cdmEntity(id + 'GroupId', businessSolution + ' Group', 'group', { viz }),
        cdmEntity(id + 'RoleId', businessSolution + ' Role', 'role', {
            apps,
            catalogs: [{ id: id + 'CatalogId' }],
            groups: [{ id: id + 'GroupId' }]
        }),
    ];
};

export const getMetadata = () => ({
    name: 'init',
    desc: 'Initialize application',
    usage: 'afctl init [PATH_TO_APP [--name APPLICATION] [--version VERSION], ...] ' +
           '[--business-solution BUSINESS_SOLUTION] [--cdm] [-f]',
    options: {
        path: {
            keys: [],
            type: 'string',
            mandatory: false,
            multiple: true,
            helpLiteral: 'PATH_TO_APP',
            helpText: 'Path to frontend application directory',
            index: 0
        },
        name: {
            keys: ['--name'],
            type: 'string',
            mandatory: false,
            multiple: true,
            helpLiteral: 'APPLICATION',
            helpText: 'Name of the application',
            index: 1
        },
        version: {
            keys: ['--version'],
            type: 'string',
            mandatory: false,
            multiple: true,
            helpLiteral: 'VERSION',
            helpText: 'Name of the application version',
            index: 2
        },
        businessSolution: {
            keys: ['--business-solution'],
            type: 'string',
            mandatory: false,
            multiple: false,
            helpLiteral: 'BUSINESS_SOLUTION',
            helpText: 'Name of the application business solution',
            index: 3
        },
        cdm: {
            keys: ['--cdm'],
            type: 'boolean',
            mandatory: false,
            multiple: false,
            helpLiteral: '',
            helpText: 'Generate cdm.json file',
            index: 4
        },
        force: {
            keys: ['--force','-f'],
            type: 'boolean',
            mandatory: false,
            multiple: false,
            helpLiteral: '',
            helpText: 'Force init with default values',
            index: 5
        }
    }
});

export const run = async ({args, env, prompt, console, fs}) => {
    if (
        args.path?.length > 0 &&
        (
            args.path.length < args.name?.length ||
            args.path.length < args.version?.length
        )
    ) {
        const err = new Error(
            'Invalid number of arguments. ' +
            'Number of paths to applications can\'t be less than provided ' +
            'number of application names or versions.'
        );
        throw err;
    }
    const writeJsonFile = getJsonFileWriter({fs, console});
    const paths = !args.path || args.path.length === 0 ? [ cwd() ] : args.path.map(p => resolve(p));
    const manifests = [];
    let businessSolution = args.businessSolution || (args.force && args.cdm && defaultBusinessSolution) || '';
    if (args.cdm && !businessSolution) {
        console.debug('Prompting business service');
        businessSolution = await prompt({
            message: 'Business service',
            default: defaultBusinessSolution
        });
        console.debug(`Business service '${businessSolution}' will be used to generate CDM`);
    }
    for (const [idx, path] of paths.entries()) {
        try {
            const stats = await fs.stat(path);
            // Not a directory
            if (!stats.isDirectory()) {
                const err = new Error(
                    `The '${path}' exists, but it's not a directory. ` +
                    'Can\'t initialize application there.'
                );
                throw err;
            }
        } catch(err) {
            // Not found
            if (err.code === 'ENOENT') {
                await fs.mkdir(path, { recursive: true });
            } else {
                // re-throw for other errors
                throw err;
            }
        }
        console.debug(`Initializing application in ${path}`);
        const name = basename(path);
        // Resource path (use ./webapp, if exists)
        let resourcePath;
        try {
            const stats = await fs.stat(join(path, 'webapp'));
            if (stats.isDirectory()) {
                resourcePath = 'webapp';
            }
        } catch(err) {
            if (err.code !== 'ENOENT') {
                throw err;
            }
        }
        resourcePath ??= '.';
        console.debug(`Using '${resourcePath}' as resource path for '${path}'`);
        // === manifest.json ===
        const manifestTemplate = env.MANIFEST_TEMPLATE ?? join(path, resourcePath, 'manifest.json');
        let manifest;
        try {
            // Try to read existing manifest.json
            manifest = JSON.parse(await fs.readFile(manifestTemplate));
        } catch(err) {
            if (err.code === 'ENOENT') {
                // Create empty manifest.json
                manifest = {};
            } else {
                // re-throw for other errors
                throw err;
            }
        }
        let application = args.name?.[idx] ?? manifest['sap.app']?.id;
        let version = args.version?.[idx] ?? manifest['sap.app']?.applicationVersion?.version;
        if (args.force) {
            console.debug('The --force flag was provided. Setting default application name and version');
            application ||= name;
            version ||= defaultVersionName;
            console.debug(
                `Default application name is '${application}', `+
                `and default version is '${version}' for path '${path}'`
            );
        }
        if (!application) {
            console.debug('Prompting application name');
            application = await prompt({
                message: `Application name for '${path}'`,
                default: name
            });
            console.debug(`The application name that will be used for path '${path}' is '${application}'`);
        }
        if (!version) {
            console.debug('Prompting application version name');
            version = await prompt({
                message: `Version name for '${path}'`,
                default: defaultVersionName
            });
            console.debug(`The application version name that will be used for path '${path}' is '${application}'`);
        }
        manifest['sap.app'] ??= {};
        manifest['sap.app'].id = application;
        manifest['sap.app'].applicationVersion ??= {};
        manifest['sap.app'].applicationVersion.version = version;
        if (businessSolution) {
            manifest['sap.cloud'] ??= {};
            manifest['sap.cloud'].service = businessSolution;
        }
        manifests.push(manifest);
        // === welcomeFile ===
        let welcomeFile;
        try {
            const stats = await fs.stat(join(path, resourcePath, 'index.html'));
            if (stats.isFile()) {
                welcomeFile = 'index.html';
            }
        } catch(err) {
            if (err.code !== 'ENOENT') {
                throw err;
            }
        }
        welcomeFile ??= 'manifest.json';
        // === xs-app.json ===
        const xsAppJsonTemplate = env.XSAPP_TEMPLATE ?? join(path, 'xs-app.json');
        let xsapp;
        try {
            // Try to read existing xs-app.json
            xsapp = JSON.parse(await fs.readFile(xsAppJsonTemplate));
        } catch(err) {
            if (err.code === 'ENOENT') {
                // Create empty xs-app.json
                xsapp = {};
            } else {
                // re-throw for other errors
                throw err;
            }
        }
        if (!xsapp.welcomeFile) {
            xsapp.welcomeFile = welcomeFile;
        }
        if (!xsapp.routes) {
            xsapp.routes = [];
        }
        let hasAppFrontRoute = false;
        xsapp.routes.forEach(route => {
            if (route.service === 'html5-apps-repo-rt') {
                route.service = 'app-front';
                hasAppFrontRoute = true;
            } else if (route.service === 'app-front') {
                hasAppFrontRoute = true;
            }
        });
        if (!hasAppFrontRoute) {
            xsapp.routes.push(defaultRoute);
        }
        // write files
        await Promise.all([
            writeJsonFile(join(path, resourcePath, 'manifest.json'), manifest),
            writeJsonFile(join(path, 'xs-app.json'), xsapp)
        ]);
    };
    // === cdm.json ===
    if (args.cdm) {
        await writeJsonFile(join(cwd(), 'cdm.json'), getCDM(manifests));
    }
};
