const path = require('path');
const fs = require('fs');
const express = require('express');
const { default: simpleGit } = require('simple-git');
const sanitize = require('sanitize-filename');
const { DIRECTORIES } = require('../constants');
const { jsonParser } = require('../express-common');

/**
 * This function extracts the extension information from the manifest file.
 * @param {string} extensionPath - The path of the extension folder
 * @returns {Promise<Object>} - Returns the manifest data as an object
 */
async function getManifest(extensionPath) {
    const manifestPath = path.join(extensionPath, 'manifest.json');

    // Check if manifest.json exists
    if (!fs.existsSync(manifestPath)) {
        throw new Error(`Manifest file not found at ${manifestPath}`);
    }

    const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
    return manifest;
}

/**
 * This function checks if the local repository is up-to-date with the remote repository.
 * @param {string} extensionPath - The path of the extension folder
 * @returns {Promise<Object>} - Returns the extension information as an object
 */
async function checkIfRepoIsUpToDate(extensionPath) {
    const git = simpleGit();
    await git.cwd(extensionPath).fetch('origin');
    const currentBranch = await git.cwd(extensionPath).branch();
    const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
    const log = await git.cwd(extensionPath).log({
        from: currentCommitHash,
        to: `origin/${currentBranch.current}`,
    });

    // Fetch remote repository information
    const remotes = await git.cwd(extensionPath).getRemotes(true);

    return {
        isUpToDate: log.total === 0,
        remoteUrl: remotes[0].refs.fetch, // URL of the remote repository
    };
}

const router = express.Router();

/**
 * HTTP POST handler function to clone a git repository from a provided URL, read the extension manifest,
 * and return extension information and path.
 *
 * @param {Object} request - HTTP Request object, expects a JSON body with a 'url' property.
 * @param {Object} response - HTTP Response object used to respond to the HTTP request.
 *
 * @returns {void}
 */
router.post('/install', jsonParser, async (request, response) => {
    if (!request.body.url) {
        return response.status(400).send('Bad Request: URL is required in the request body.');
    }

    try {
        const git = simpleGit();

        // make sure the third-party directory exists
        if (!fs.existsSync(path.join(DIRECTORIES.extensions, 'third-party'))) {
            fs.mkdirSync(path.join(DIRECTORIES.extensions, 'third-party'));
        }

        const url = request.body.url;
        const extensionPath = path.join(DIRECTORIES.extensions, 'third-party', path.basename(url, '.git'));

        if (fs.existsSync(extensionPath)) {
            return response.status(409).send(`Directory already exists at ${extensionPath}`);
        }

        await git.clone(url, extensionPath, { '--depth': 1 });
        console.log(`Extension has been cloned at ${extensionPath}`);


        const { version, author, display_name } = await getManifest(extensionPath);


        return response.send({ version, author, display_name, extensionPath });
    } catch (error) {
        console.log('Importing custom content failed', error);
        return response.status(500).send(`Server Error: ${error.message}`);
    }
});

/**
 * HTTP POST handler function to pull the latest updates from a git repository
 * based on the extension name provided in the request body. It returns the latest commit hash,
 * the path of the extension, the status of the repository (whether it's up-to-date or not),
 * and the remote URL of the repository.
 *
 * @param {Object} request - HTTP Request object, expects a JSON body with an 'extensionName' property.
 * @param {Object} response - HTTP Response object used to respond to the HTTP request.
 *
 * @returns {void}
 */
router.post('/update', jsonParser, async (request, response) => {
    const git = simpleGit();
    if (!request.body.extensionName) {
        return response.status(400).send('Bad Request: extensionName is required in the request body.');
    }

    try {
        const extensionName = request.body.extensionName;
        const extensionPath = path.join(DIRECTORIES.extensions, 'third-party', extensionName);

        if (!fs.existsSync(extensionPath)) {
            return response.status(404).send(`Directory does not exist at ${extensionPath}`);
        }

        const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath);
        const currentBranch = await git.cwd(extensionPath).branch();
        if (!isUpToDate) {

            await git.cwd(extensionPath).pull('origin', currentBranch.current);
            console.log(`Extension has been updated at ${extensionPath}`);
        } else {
            console.log(`Extension is up to date at ${extensionPath}`);
        }
        await git.cwd(extensionPath).fetch('origin');
        const fullCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
        const shortCommitHash = fullCommitHash.slice(0, 7);

        return response.send({ shortCommitHash, extensionPath, isUpToDate, remoteUrl });

    } catch (error) {
        console.log('Updating custom content failed', error);
        return response.status(500).send(`Server Error: ${error.message}`);
    }
});

/**
 * HTTP POST handler function to get the current git commit hash and branch name for a given extension.
 * It checks whether the repository is up-to-date with the remote, and returns the status along with
 * the remote URL of the repository.
 *
 * @param {Object} request - HTTP Request object, expects a JSON body with an 'extensionName' property.
 * @param {Object} response - HTTP Response object used to respond to the HTTP request.
 *
 * @returns {void}
 */
router.post('/version', jsonParser, async (request, response) => {
    const git = simpleGit();
    if (!request.body.extensionName) {
        return response.status(400).send('Bad Request: extensionName is required in the request body.');
    }

    try {
        const extensionName = request.body.extensionName;
        const extensionPath = path.join(DIRECTORIES.extensions, 'third-party', extensionName);

        if (!fs.existsSync(extensionPath)) {
            return response.status(404).send(`Directory does not exist at ${extensionPath}`);
        }

        const currentBranch = await git.cwd(extensionPath).branch();
        // get only the working branch
        const currentBranchName = currentBranch.current;
        await git.cwd(extensionPath).fetch('origin');
        const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
        console.log(currentBranch, currentCommitHash);
        const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath);

        return response.send({ currentBranchName, currentCommitHash, isUpToDate, remoteUrl });

    } catch (error) {
        console.log('Getting extension version failed', error);
        return response.status(500).send(`Server Error: ${error.message}`);
    }
});

/**
 * HTTP POST handler function to delete a git repository based on the extension name provided in the request body.
 *
 * @param {Object} request - HTTP Request object, expects a JSON body with a 'url' property.
 * @param {Object} response - HTTP Response object used to respond to the HTTP request.
 *
 * @returns {void}
 */
router.post('/delete', jsonParser, async (request, response) => {
    if (!request.body.extensionName) {
        return response.status(400).send('Bad Request: extensionName is required in the request body.');
    }

    // Sanatize the extension name to prevent directory traversal
    const extensionName = sanitize(request.body.extensionName);

    try {
        const extensionPath = path.join(DIRECTORIES.extensions, 'third-party', extensionName);

        if (!fs.existsSync(extensionPath)) {
            return response.status(404).send(`Directory does not exist at ${extensionPath}`);
        }

        await fs.promises.rmdir(extensionPath, { recursive: true });
        console.log(`Extension has been deleted at ${extensionPath}`);

        return response.send(`Extension has been deleted at ${extensionPath}`);

    } catch (error) {
        console.log('Deleting custom content failed', error);
        return response.status(500).send(`Server Error: ${error.message}`);
    }
});

/**
 * Discover the extension folders
 * If the folder is called third-party, search for subfolders instead
 */
router.get('/discover', jsonParser, function (_, response) {
    // get all folders in the extensions folder, except third-party
    const extensions = fs
        .readdirSync(DIRECTORIES.extensions)
        .filter(f => fs.statSync(path.join(DIRECTORIES.extensions, f)).isDirectory())
        .filter(f => f !== 'third-party');

    // get all folders in the third-party folder, if it exists

    if (!fs.existsSync(path.join(DIRECTORIES.extensions, 'third-party'))) {
        return response.send(extensions);
    }

    const thirdPartyExtensions = fs
        .readdirSync(path.join(DIRECTORIES.extensions, 'third-party'))
        .filter(f => fs.statSync(path.join(DIRECTORIES.extensions, 'third-party', f)).isDirectory());

    // add the third-party extensions to the extensions array
    extensions.push(...thirdPartyExtensions.map(f => `third-party/${f}`));
    console.log(extensions);


    return response.send(extensions);
});

module.exports = { router };
