// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

import assert = require("assert");
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import Sinon = require("sinon");
import proxyquire = require("proxyquire");

suite("enableHermesCommand", function () {
    let tempDir: string;

    function removeDirRecursive(dirPath: string): void {
        if (fs.existsSync(dirPath)) {
            fs.readdirSync(dirPath).forEach(file => {
                const currentPath = path.join(dirPath, file);
                if (fs.lstatSync(currentPath).isDirectory()) {
                    removeDirRecursive(currentPath);
                } else {
                    fs.unlinkSync(currentPath);
                }
            });
            fs.rmdirSync(dirPath);
        }
    }

    function createMockProject(projectPath: string): any {
        return {
            getPackager: () => ({
                getProjectPath: () => projectPath,
            }),
        };
    }

    function createCommandModule(
        showQuickPickStub: Sinon.SinonStub,
        writeFileStub: Sinon.SinonStub,
        spawnStub: Sinon.SinonStub,
        logger = {
            info: Sinon.stub(),
            error: Sinon.stub(),
            warning: Sinon.stub(),
            debug: Sinon.stub(),
        },
    ) {
        class FakeFileSystem {
            public writeFile = writeFileStub;
        }

        class FakeCommandExecutor {
            public spawn = spawnStub;
        }

        const module = proxyquire.noCallThru()("../../../src/extension/commands/enableHermes", {
            vscode: {
                window: {
                    showQuickPick: showQuickPickStub,
                },
            },
            "../../common/node/fileSystem": {
                FileSystem: FakeFileSystem,
            },
            "../../common/commandExecutor": {
                CommandExecutor: FakeCommandExecutor,
            },
            "../appLauncher": {
                AppLauncher: {
                    getNodeModulesRootByProjectPath: () => path.join(tempDir, "node_modules"),
                },
            },
            "../log/OutputChannelLogger": {
                OutputChannelLogger: {
                    getMainChannel: () => logger,
                },
            },
        }) as typeof import("../../../src/extension/commands/enableHermes");

        return {
            EnableHermes: module.EnableHermes,
            logger,
        };
    }

    async function runCommand(
        commandClass: typeof import("../../../src/extension/commands/enableHermes").EnableHermes,
    ): Promise<void> {
        const command = commandClass.formInstance();
        (command as any).project = createMockProject(tempDir);
        await command.baseFn();
    }

    setup(function () {
        tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "enableHermes-test-"));
    });

    teardown(function () {
        removeDirRecursive(tempDir);
    });

    test("should warn when iOS Podfile is missing", async function () {
        const showQuickPickStub = Sinon.stub();
        showQuickPickStub.onFirstCall().returns(Promise.resolve("iOS"));
        showQuickPickStub.onSecondCall().returns(Promise.resolve("true"));
        const writeFileStub = Sinon.stub().returns(Promise.resolve());
        const spawnStub = Sinon.stub().returns(Promise.resolve());
        const { EnableHermes, logger } = createCommandModule(
            showQuickPickStub,
            writeFileStub,
            spawnStub,
        );

        await runCommand(EnableHermes);

        assert.strictEqual(logger.warning.calledWithExactly("Podfile not found"), true);
        assert.strictEqual(writeFileStub.called, false);
        assert.strictEqual(spawnStub.called, false);
    });

    test("should update Android hermesEnabled property", async function () {
        const androidPath = path.join(tempDir, "android");
        const gradleFilePath = path.join(androidPath, "gradle.properties");
        fs.mkdirSync(androidPath, { recursive: true });
        fs.writeFileSync(gradleFilePath, "android.useAndroidX=true\nhermesEnabled=false");
        const showQuickPickStub = Sinon.stub();
        showQuickPickStub.onFirstCall().returns(Promise.resolve("Android"));
        showQuickPickStub.onSecondCall().returns(Promise.resolve("true"));
        const writeFileStub = Sinon.stub().returns(Promise.resolve());
        const spawnStub = Sinon.stub().returns(Promise.resolve());
        const { EnableHermes } = createCommandModule(showQuickPickStub, writeFileStub, spawnStub);

        await runCommand(EnableHermes);

        assert.strictEqual(writeFileStub.calledOnce, true);
        assert.strictEqual(writeFileStub.firstCall.args[0], gradleFilePath);
        assert.strictEqual(
            writeFileStub.firstCall.args[1],
            "android.useAndroidX=true\nhermesEnabled=true",
        );
        assert.strictEqual(spawnStub.called, false);
    });

    test("should update iOS hermes flag and install pods", async function () {
        const iosPath = path.join(tempDir, "ios");
        const podfilePath = path.join(iosPath, "Podfile");
        fs.mkdirSync(iosPath, { recursive: true });
        fs.writeFileSync(
            podfilePath,
            "use_react_native!(\n    :path => config[:reactNativePath],\n    :hermes_enabled => false\n)",
        );
        const showQuickPickStub = Sinon.stub();
        showQuickPickStub.onFirstCall().returns(Promise.resolve("iOS"));
        showQuickPickStub.onSecondCall().returns(Promise.resolve("true"));
        const writeFileStub = Sinon.stub().returns(Promise.resolve());
        const spawnStub = Sinon.stub().returns(Promise.resolve());
        const { EnableHermes } = createCommandModule(showQuickPickStub, writeFileStub, spawnStub);

        await runCommand(EnableHermes);

        assert.strictEqual(writeFileStub.calledOnce, true);
        assert.strictEqual(writeFileStub.firstCall.args[0], podfilePath);
        assert.strictEqual(
            writeFileStub.firstCall.args[1].includes(":hermes_enabled => true"),
            true,
        );
        assert.strictEqual(spawnStub.calledWithExactly("pod", ["install"]), true);
    });
});
