package com.ruoyi.generator.service.front;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class RunPreviewProcessRunnerTest
{
    @Rule
    public TemporaryFolder temporaryFolder = new TemporaryFolder();

    @Test
    public void prepareFrontendEnvironmentPrependsConfiguredNodeHomeForViteProject() throws Exception
    {
        File projectDirectory = viteProject();
        File nodeHome = fakeNodeHome("v20.20.2");
        RunPreviewProcessRunner runner = new RunPreviewProcessRunner();
        runner.setNodeHomeForTesting(nodeHome.getAbsolutePath());

        Map<String, String> environment = runner.prepareFrontendEnvironment(projectDirectory, new HashMap<String, String>());

        assertTrue(environment.get(runner.pathEnvironmentKey()).startsWith(nodeHome.getAbsolutePath()));
    }

    @Test
    public void prepareFrontendEnvironmentRejectsOldConfiguredNodeForViteProject() throws Exception
    {
        File projectDirectory = viteProject();
        File nodeHome = fakeNodeHome("v14.21.3");
        RunPreviewProcessRunner runner = new RunPreviewProcessRunner();
        runner.setNodeHomeForTesting(nodeHome.getAbsolutePath());

        try
        {
            runner.prepareFrontendEnvironment(projectDirectory, new HashMap<String, String>());
            fail("Expected Node version validation to reject old Node");
        }
        catch (IOException e)
        {
            assertTrue(e.getMessage().contains("requires Node.js >= 18"));
            assertTrue(e.getMessage().contains("v14.21.3"));
        }
    }

    @Test
    public void stopManagedWindowsProcessTerminatesDescendantProcess() throws Exception
    {
        if (!isWindows())
        {
            return;
        }
        File projectDirectory = temporaryFolder.newFolder("managed-process");
        File childPidFile = new File(projectDirectory, "child.pid");
        RunPreviewProcessRunner runner = new RunPreviewProcessRunner();

        Process process = runner.startCommandForTesting(
                "powershell.exe -NoProfile -NonInteractive -Command "
                        + "\"$PID | Set-Content -NoNewline child.pid; Start-Sleep -Seconds 30\"",
                projectDirectory, new HashMap<String, String>());
        long childPid = -1L;

        try
        {
            assertTrue(process instanceof ManagedPreviewProcess);
            assertTrue(((ManagedPreviewProcess) process).waitForRootPid(2000L) > 0L);
            assertTrue(waitForFile(childPidFile, 2000L));
            childPid = Long.parseLong(new String(
                    Files.readAllBytes(childPidFile.toPath()), StandardCharsets.UTF_8).trim());
            assertTrue(isWindowsProcessAlive(childPid));
        }
        finally
        {
            new RunPreviewProcessKiller().stop(process, null);
        }

        assertFalse(process.isAlive());
        assertFalse(isWindowsProcessAlive(childPid));
    }

    private File viteProject() throws IOException
    {
        File projectDirectory = temporaryFolder.newFolder("vite-project");
        String packageJson = "{\n"
                + "  \"scripts\": { \"dev\": \"vite --host 0.0.0.0\" },\n"
                + "  \"dependencies\": { \"vite\": \"5.3.1\" }\n"
                + "}";
        Files.write(new File(projectDirectory, "package.json").toPath(), packageJson.getBytes(StandardCharsets.UTF_8));
        return projectDirectory;
    }

    private File fakeNodeHome(String version) throws IOException
    {
        File nodeHome = temporaryFolder.newFolder("node-" + version.substring(1));
        File nodeCommand = new File(nodeHome, isWindows() ? "node.cmd" : "node");
        String script = isWindows()
                ? "@echo off\r\necho " + version + "\r\n"
                : "#!/bin/sh\nprintf '%s\\n' '" + version + "'\n";
        Files.write(nodeCommand.toPath(), script.getBytes(StandardCharsets.UTF_8));
        nodeCommand.setExecutable(true);
        return nodeHome;
    }

    private boolean waitForFile(File file, long timeoutMillis) throws InterruptedException
    {
        long deadline = System.currentTimeMillis() + timeoutMillis;
        while (System.currentTimeMillis() < deadline)
        {
            if (file.isFile() && file.length() > 0L)
            {
                return true;
            }
            Thread.sleep(25L);
        }
        return file.isFile() && file.length() > 0L;
    }

    private boolean isWindowsProcessAlive(long pid) throws Exception
    {
        if (pid <= 0L)
        {
            return false;
        }
        Process check = new ProcessBuilder(
                "powershell.exe",
                "-NoProfile",
                "-NonInteractive",
                "-Command",
                "if (Get-Process -Id " + pid + " -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }")
                .redirectErrorStream(true)
                .start();
        return check.waitFor() == 0;
    }

    private boolean isWindows()
    {
        String osName = System.getProperty("os.name");
        return osName != null && osName.toLowerCase().contains("win");
    }
}
