const fs = require("fs");
const path = require("path");
const childProcess = require('child_process');
const { Command } = require("commander");
const inquirer = require("inquirer");
const { fetch } = require("undici");
const { projectsCreate, projectsGet } = require("./projects");
const { storageCreateBucket } = require("./storage");
const { messagingCreateTopic } = require("./messaging");
const { functionsCreate } = require("./functions");
const { databasesCreateCollection } = require("./databases");
const { pullResources } = require("./pull");
const ID = require("../id");
const { localConfig, globalConfig } = require("../config");
const {
    questionsCreateFunction,
    questionsCreateFunctionSelectTemplate,
    questionsCreateBucket,
    questionsCreateMessagingTopic,
    questionsCreateCollection,
    questionsInitProject,
    questionsInitProjectAutopull,
    questionsInitResources,
    questionsCreateTeam
} = require("../questions");
const { cliConfig, success, log, hint, error, actionRunner, commandDescriptions } = require("../parser");
const { accountGet } = require("./account");
const { sdkForConsole } = require("../sdks");

const initResources = async () => {
    const actions = {
        function: initFunction,
        collection: initCollection,
        bucket: initBucket,
        team: initTeam,
        message: initTopic
    }

    const answers = await inquirer.prompt(questionsInitResources[0]);

    const action = actions[answers.resource];
    if (action !== undefined) {
        await action({ returnOnZero: true });
    }
};

const initProject = async ({ organizationId, projectId, projectName } = {}) => {
    let response = {};

    try {
        if (globalConfig.getEndpoint() === '' || globalConfig.getCookie() === '') {
            throw '';
        }
        const client = await sdkForConsole();

        await accountGet({
            parseOutput: false,
            sdk: client
        });
    } catch (e) {
        error("Error Session not found. Please run 'appwrite login' to create a session");
        process.exit(1);
    }

    let answers = {};

    if (!organizationId && !projectId && !projectName) {
        answers = await inquirer.prompt(questionsInitProject)
        if (answers.override === false) {
            process.exit(1)
        }
    } else {
        answers.start = 'existing';
        answers.project = {};
        answers.organization = {};

        answers.organization = organizationId ?? (await inquirer.prompt(questionsInitProject[2])).organization;
        answers.project.name = projectName ?? (await inquirer.prompt(questionsInitProject[3])).project;
        answers.project = projectId ?? (await inquirer.prompt(questionsInitProject[4])).id;

        try {
            await projectsGet({ projectId, parseOutput: false });
        } catch (e) {
            if (e.code === 404) {
                answers.start = 'new';
                answers.id = answers.project;
                answers.project = answers.project.name;
            } else {
                throw e;
            }
        }
    }

    if (answers.start === 'new') {
        response = await projectsCreate({
            projectId: answers.id,
            name: answers.project,
            teamId: answers.organization,
            parseOutput: false
        })

        localConfig.setProject(response['$id']);
    } else {
        localConfig.setProject(answers.project);
    }

    success(`Project successfully ${answers.start === 'existing' ? 'linked' : 'created'}. Details are now stored in appwrite.json file.`);

    if(answers.start === 'existing') {
        answers = await inquirer.prompt(questionsInitProjectAutopull);
        if(answers.autopull) {
            cliConfig.all = true;
            cliConfig.force = true;
            await pullResources();
        } else {
            log("You can run 'appwrite pull all' to synchronize all of your existing resources.");
        }
    }

    hint("Next you can use 'appwrite init' to create resources in your project, or use 'appwrite pull' and 'appwrite push' to synchronize your project.")
}

const initBucket = async () => {
    const answers = await inquirer.prompt(questionsCreateBucket)

    localConfig.addBucket({
        $id: answers.id === 'unique()' ? ID.unique() : answers.id,
        name: answers.bucket,
        fileSecurity: answers.fileSecurity.toLowerCase() === 'yes',
        enabled: true,
    });
    success("Initialing bucket");
    log("Next you can use 'appwrite push bucket' to deploy the changes.");
};

const initTeam = async () => {
    const answers = await inquirer.prompt(questionsCreateTeam)

    localConfig.addTeam({
        $id: answers.id === 'unique()' ? ID.unique() : answers.id,
        name: answers.bucket,
    });

    success("Initialing team");
    log("Next you can use 'appwrite push team' to deploy the changes.");
};

const initCollection = async () => {
    const answers = await inquirer.prompt(questionsCreateCollection)
    const newDatabase = (answers.method ?? '').toLowerCase() !== 'existing';

    if (!newDatabase) {
        answers.databaseId = answers.database;
        answers.databaseName = localConfig.getDatabase(answers.database).name;
    }

    const databaseId = answers.databaseId === 'unique()' ? ID.unique() : answers.databaseId;

    if (newDatabase || !localConfig.getDatabase(answers.databaseId)) {
        localConfig.addDatabase({
            $id: databaseId,
            name: answers.databaseName,
            enabled: true
        });
    }

    localConfig.addCollection({
        $id: answers.id === 'unique()' ? ID.unique() : answers.id,
        databaseId: databaseId,
        name: answers.collection,
        documentSecurity: answers.documentSecurity.toLowerCase() === 'yes',
        attributes: [],
        indexes: [],
        enabled: true,
    });

    success("Initialing collection");
    log("Next you can use 'appwrite push collection' to deploy the changes.");
};

const initTopic = async () => {
    const answers = await inquirer.prompt(questionsCreateMessagingTopic)

    localConfig.addMessagingTopic({
        $id: answers.id === 'unique()' ? ID.unique() : answers.id,
        name: answers.topic,

    });

    success("Initialing topic");
    log("Next you can use 'appwrite push topic' to deploy the changes.");
};

const initFunction = async () => {
    process.chdir(localConfig.configDirectoryPath)

    // TODO: Add CI/CD support (ID, name, runtime)
    const answers = await inquirer.prompt(questionsCreateFunction)
    const functionFolder = path.join(process.cwd(), 'functions');

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

    const functionId = answers.id === 'unique()' ? ID.unique() : answers.id;
    const functionName = answers.name;
    const functionDir = path.join(functionFolder, functionName);
    const templatesDir = path.join(functionFolder, `${functionId}-templates`);
    const runtimeDir = path.join(templatesDir, answers.runtime.name);

    if (fs.existsSync(functionDir)) {
        throw new Error(`( ${functionName} ) already exists in the current directory. Please choose another name.`);
    }

    if (!answers.runtime.entrypoint) {
        log(`Entrypoint for this runtime not found. You will be asked to configure entrypoint when you first push the function.`);
    }

    if (!answers.runtime.commands) {
        log(`Installation command for this runtime not found. You will be asked to configure the install command when you first push the function.`);
    }

    fs.mkdirSync(functionDir, "777");
    fs.mkdirSync(templatesDir, "777");
    const repo = "https://github.com/{{ sdk.gitUserName }}/templates";
    const api = `https://api.github.com/repos/{{ sdk.gitUserName }}/templates/contents/${answers.runtime.name}`
    let selected = { template: 'starter' };

    const sparse = (selected ? `${answers.runtime.name}/${selected.template}` : answers.runtime.name).toLowerCase();

    let gitInitCommands = `git clone --single-branch --depth 1 --sparse ${repo} .`; // depth prevents fetching older commits reducing the amount fetched

    let gitPullCommands = `git sparse-checkout add ${sparse}`;

    /* Force use CMD as powershell does not support && */
    if (process.platform === 'win32') {
        gitInitCommands = 'cmd /c "' + gitInitCommands + '"';
        gitPullCommands = 'cmd /c "' + gitPullCommands + '"';
    }

    /*  Execute the child process but do not print any std output */
    try {
        childProcess.execSync(gitInitCommands, { stdio: 'pipe', cwd: templatesDir });
        childProcess.execSync(gitPullCommands, { stdio: 'pipe', cwd: templatesDir });
    } catch (error) {
        /* Specialised errors with recommended actions to take */
        if (error.message.includes('error: unknown option')) {
            throw new Error(`${error.message} \n\nSuggestion: Try updating your git to the latest version, then trying to run this command again.`)
        } else if (error.message.includes('is not recognized as an internal or external command,') || error.message.includes('command not found')) {
            throw new Error(`${error.message} \n\nSuggestion: It appears that git is not installed, try installing git then trying to run this command again.`)
        } else {
            throw error;
        }
    }

    fs.rmSync(path.join(templatesDir, ".git"), { recursive: true });
    if (!selected) {
        const templates = [];
        templates.push(...fs.readdirSync(runtimeDir, { withFileTypes: true })
            .filter(item => item.isDirectory() && item.name !== 'starter')
            .map(dirent => dirent.name));
        selected = { template: 'starter' };

        if (templates.length > 1) {
            selected = await inquirer.prompt(questionsCreateFunctionSelectTemplate(templates))
        }
    }

    const copyRecursiveSync = (src, dest) => {
        let exists = fs.existsSync(src);
        let stats = exists && fs.statSync(src);
        let isDirectory = exists && stats.isDirectory();
        if (isDirectory) {
            if (!fs.existsSync(dest)) {
                fs.mkdirSync(dest);
            }

            fs.readdirSync(src).forEach(function (childItemName) {
                copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName));
            });
        } else {
            fs.copyFileSync(src, dest);
        }
    };
    copyRecursiveSync(path.join(runtimeDir, selected.template), functionDir);

    fs.rmSync(templatesDir, { recursive: true, force: true });

    const readmePath = path.join(process.cwd(), 'functions', functionName, 'README.md');
    const readmeFile = fs.readFileSync(readmePath).toString();
    const newReadmeFile = readmeFile.split('\n');
    newReadmeFile[0] = `# ${answers.name}`;
    newReadmeFile.splice(1, 2);
    fs.writeFileSync(readmePath, newReadmeFile.join('\n'));

    let data = {
        $id: functionId,
        name: answers.name,
        runtime: answers.runtime.id,
        execute: ["any"],
        events: [],
        scopes: ["users.read"],
        schedule: "",
        timeout: 15,
        enabled: true,
        logging: true,
        entrypoint: answers.runtime.entrypoint || '',
        commands: answers.runtime.commands || '',
        ignore: answers.runtime.ignore || null,
        path: `functions/${functionName}`,
    };

    localConfig.addFunction(data);
    success("Initialing function");
    log("Next you can use 'appwrite run function' to develop a function locally. To deploy the function, use 'appwrite push function'");
}

const init = new Command("init")
    .description(commandDescriptions['init'])
    .action(actionRunner(initResources));

init
    .command("project")
    .description("Init a new {{ spec.title|caseUcfirst }} project")
    .option("--organization-id <organization-id>", "{{ spec.title|caseUcfirst }} organization ID")
    .option("--project-id <project-id>", "{{ spec.title|caseUcfirst }} project ID")
    .option("--project-name <project-name>", "{{ spec.title|caseUcfirst }} project ID")
    .action(actionRunner(initProject));

init
    .command("function")
    .alias("functions")
    .description("Init a new {{ spec.title|caseUcfirst }} function")
    .action(actionRunner(initFunction));

init
    .command("bucket")
    .alias("buckets")
    .description("Init a new {{ spec.title|caseUcfirst }} bucket")
    .action(actionRunner(initBucket));

init
    .command("team")
    .alias("teams")
    .description("Init a new {{ spec.title|caseUcfirst }} team")
    .action(actionRunner(initTeam));

init
    .command("collection")
    .alias("collections")
    .description("Init a new {{ spec.title|caseUcfirst }} collection")
    .action(actionRunner(initCollection));

init
    .command("topic")
    .alias("topics")
    .description("Init a new {{ spec.title|caseUcfirst }} topic")
    .action(actionRunner(initTopic));

module.exports = {
    init,
};
