import * as path from "path";
import { _electron as electron, ElectronApplication, Page } from "playwright";
import {
    downloadAndUnzipVSCode,
    resolveCliArgsFromVSCodeExecutablePath,
} from "@vscode/test-electron";
import * as utilities from "./utilities";
import { SmokeTestLogger } from "./smokeTestLogger";
import * as fs from "fs";
import rimraf = require("rimraf");
import { Element } from "./constants";
import { TimeoutConstants } from "./timeoutConstants";

export class Application {
    private app: ElectronApplication | null = null;
    private mainPage: Page | null = null;
    private vscodeExecutablePath: string | null = null;
    private isExtensionActivited: boolean = false;
    private extensionDirectory = path.join(__dirname, "..", "..", ".vscode-test", "extensions");
    private userDataDirectory = path.join(__dirname, "..", "..", ".vscode-test", "temp-user-data");
    private vsixDirectory = path.join(__dirname, "..", "..", "resources", "extension");
    private projectPath = path.join(__dirname, "..", "..", "resources", "sampleReactNativeProject");
    private vscodeVersion = process.env.SMOKE_TEST_VSCODE_VERSION || "1.107.1";

    private async ensureSmokeEnvironmentPrepared(): Promise<void> {
        if (this.vscodeExecutablePath) {
            return;
        }

        SmokeTestLogger.info(
            "VSCode executable path is not initialized. Preparing smoke environment in launch().",
        );
        const vscodeExecutablePath = await this.downloadVSCodeExecutable();
        this.setVSCodeExecutablePath(vscodeExecutablePath);
        await this.cleanUserData();
        await this.installExtensionFromVSIX(vscodeExecutablePath);
    }

    private async waitForWorkbenchWindow(timeout: number): Promise<Page> {
        if (!this.app) {
            throw new Error("VSCode has not been launched yet.");
        }

        const startTime = Date.now();
        while (Date.now() - startTime < timeout) {
            const windows = this.app.windows();
            for (const window of windows) {
                try {
                    await window.waitForSelector(`.${Element.vscodeWorkbenchClassName}`, {
                        timeout: 1000,
                    });
                    return window;
                } catch {
                    continue;
                }
            }

            await utilities.sleep(500);
        }

        throw new Error("Timed out waiting for VSCode workbench window.");
    }

    async downloadVSCodeExecutable(): Promise<string> {
        const vscodeExecutablePath = await downloadAndUnzipVSCode(this.vscodeVersion);
        return vscodeExecutablePath;
    }

    async launch(): Promise<Page> {
        if (this.mainPage) return this.mainPage;

        await this.ensureSmokeEnvironmentPrepared();

        const vscodeExecutablePath = this.vscodeExecutablePath;
        if (!vscodeExecutablePath) {
            throw new Error("VSCode executable path is not available after smoke setup.");
        }
        const [...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);
        args.push("--disable-workspace-trust");
        args.push("--no-sandbox");

        const userDataIndex = args.findIndex(arg => arg.startsWith("--user-data-dir="));
        if (userDataIndex >= 0) {
            args[userDataIndex] = `--user-data-dir=${this.userDataDirectory}`;
        } else {
            args.push(`--user-data-dir=${this.userDataDirectory}`);
        }

        this.app = await electron.launch({
            executablePath: vscodeExecutablePath,
            args: [this.projectPath, ...args],
        });

        this.mainPage = await this.waitForWorkbenchWindow(30000);

        await utilities.sleep(TimeoutConstants.APPLICATION_INIT_SLEEP);
        return this.mainPage;
    }

    async close(): Promise<void> {
        try {
            await this.cleanUserData();
        } catch {
            SmokeTestLogger.info("Cannot clean up user data, will try it again in test setup.");
        }

        if (this.app) {
            await this.app.close();
            this.app = null;
            this.mainPage = null;
        }
    }

    async installExtensionFromVSIX(vscodeExecutablePath: string): Promise<void> {
        const [cliPath, ...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);
        // Remove existing --extensions-dir if present to avoid duplication
        const extensionsDirIndex = args.findIndex(arg => arg.startsWith("--extensions-dir="));
        if (extensionsDirIndex >= 0) {
            args[extensionsDirIndex] = `--extensions-dir=${this.extensionDirectory}`;
        } else {
            args.push(`--extensions-dir=${this.extensionDirectory}`);
        }
        let extensionFile = utilities.findFile(this.vsixDirectory, /.*\.(vsix)/);
        if (!extensionFile) {
            throw new Error(`React Native extension .vsix is not found in ${this.vsixDirectory}`);
        }

        extensionFile = path.join(this.vsixDirectory, extensionFile);
        args.push(`--install-extension=${extensionFile}`);

        if (process.platform == "win32") {
            utilities.spawnSync(cliPath, args, { stdio: "inherit", shell: true });
        } else {
            utilities.spawnSync(cliPath, args, { stdio: "inherit" });
        }
    }

    getMainPage(): Page {
        if (!this.mainPage) {
            throw new Error("VSCode has not been launched yet.");
        }
        return this.mainPage;
    }

    setVSCodeExecutablePath(vscodeExecutablePath: string) {
        this.vscodeExecutablePath = vscodeExecutablePath;
    }

    setExtensionStatus(isActivited: boolean) {
        this.isExtensionActivited = isActivited;
    }

    getExtensionStatus(): boolean {
        if (!this.isExtensionActivited) {
            throw new Error("Cannot get extension status.");
        }
        return this.isExtensionActivited;
    }

    async cleanUserData(): Promise<void> {
        if (fs.existsSync(this.userDataDirectory)) {
            SmokeTestLogger.info(
                `Deleting VSCode temporary user data dir: ${this.userDataDirectory}`,
            );
            rimraf.sync(this.userDataDirectory);
        }
    }

    async cleanExtensionData(): Promise<void> {
        if (fs.existsSync(this.extensionDirectory)) {
            SmokeTestLogger.log(`Deleting VS Code extension dir: ${this.extensionDirectory}`);
            rimraf.sync(this.extensionDirectory);
        }
    }
}
