import os
import subprocess
import sys
from pathlib import Path

import psutil

from clarabot_nano.core.spawn.poll import (
    AbstractSpawnHealthPoller,
    AbstractPolledSpawnWithThread,
    AbstractSpawnHealthHTTPPoller,
)
from clarabot_nano.core.spawn.track import SubprocessTracker
from clarabot_nano.resource import get_resource_path
from clarabot_nano.utility.network import get_free_port_for_dynamic_service
from clarabot_nano.utility.path.java import java_compatible_absolute_path


class PicotureSpawnHealthPoller(AbstractSpawnHealthHTTPPoller):
    def __init__(self, spawn):
        AbstractSpawnHealthHTTPPoller.__init__(self, spawn)

    def _check_process_health(self, spawn) -> bool:
        try:
            with self._session.get(f"http://{spawn.host}:{spawn.port}/version", timeout=8.0) as r:
                if r.status_code == 200:
                    return r.text.startswith("Picoture ")
        except Exception:
            pass
        return False


class PicotureSpawn(AbstractPolledSpawnWithThread):
    PID_PREFIX = "picoture"

    def __init__(
        self,
        java_home: str,
        java_executable: str,
        picoture_jar: str,
        picoture_logs_dir: Path,
        picoture_pid_dir: Path,
        picoture_heap_size: str,
    ):
        # home & executable shall be supplied in java-compatible paths
        self._java_home = java_home
        self._java_executable = java_executable
        self._picoture_jar = java_compatible_absolute_path(picoture_jar)
        self._picoture_dist_dir = java_compatible_absolute_path(os.path.dirname(picoture_jar))
        self._picoture_logs_dir = java_compatible_absolute_path(picoture_logs_dir)
        self._picoture_pid_dir = picoture_pid_dir
        self._picoture_heap_size = picoture_heap_size
        self.host = "127.0.0.1"
        self.port = get_free_port_for_dynamic_service()
        AbstractPolledSpawnWithThread.__init__(self)

    def _create_health_poller(self) -> AbstractSpawnHealthPoller:
        return PicotureSpawnHealthPoller(self)

    def kill(self):
        """
        The "stop" command already includes a kill after a timeout so nothing to be done here.
        """
        pass

    def get_pid_prefix(self) -> str:
        return self.PID_PREFIX

    def _stop_spawn(self, *args):
        for _ in SubprocessTracker.cleanup(
            self._picoture_pid_dir, kill_timeout=10.0, pid_file_prefix=self.get_pid_prefix()
        ):
            pass  # cannot ask graceful shutdown though api, just return and do it by terminate-signal

    def _start_spawn(self, *args) -> psutil.Popen:
        # prepare spawn
        picoture_spawn_config_dir = Path(java_compatible_absolute_path(get_resource_path("spawn", "picoture")))
        logs_dir = Path(self._picoture_logs_dir)
        logs_dir.mkdir(parents=True, exist_ok=True)
        env = dict(os.environ)
        # If the columns is not specified, argparse4j would try to call a system shell to get the terminal width in
        # characters. Just for this cosmetic "feature" we're not letting it access a shell, so we specify the "COLUMNS"
        # environment variable with the value that indicates an unspecified width.
        env["COLUMNS"] = "-1"
        if self._java_home:
            env["JAVA_HOME"] = self._java_home
        java_mem = [f"-Xms{self._picoture_heap_size}", f"-Xmx{self._picoture_heap_size}"]
        gc_tune = [
            "-XX:+UseG1GC",
            "-XX:+PerfDisableSharedMem",
            "-XX:+ParallelRefProcEnabled",
            "-XX:MaxGCPauseMillis=250",
            "-XX:+AlwaysPreTouch",
        ]
        if not sys.platform.startswith("win"):
            gc_tune.append("-XX:+UseLargePages")
        java_security_manager = [
            # "-Djava.security.debug=access",  # for development
            "-Djava.security.manager",
            f"-Djava.security.policy={picoture_spawn_config_dir / 'security.policy'}",
            # f"-Djava.security.properties={picoture_spawn_config_dir / 'security.properties'}",
        ]
        log_args = [
            # "-Dlog4j2.debug=true",  # for development
            f"-Dlog4j2.configurationFile={picoture_spawn_config_dir / 'log4j2.xml'}",
            "-Dlog4j2.formatMsgNoLookups=true",  # mitigate CVE-2021-44228 for Log4j2<=2.14.1 and Java<=8u121
        ]
        # Compile launch args for the picoture server.
        launch_args = [
            self._java_executable,
            *java_mem,
            *gc_tune,
            *java_security_manager,
            *log_args,
            f"-Dnano.dist.picoture={self._picoture_dist_dir}",
            f"-Dnano.config.picoture={picoture_spawn_config_dir}",
            f"-Dpicoture.logs.dir={self._picoture_logs_dir}",
            # f"-Xlog:gc*:file={self._picoture_logs_dir}/picoture_gc.log:time,uptime:filecount=3,filesize=20M",
            "-jar",
            self._picoture_jar,
            "--host",
            self.host,
            "--port",
            str(self.port),
        ]
        with open(logs_dir / "picoture-stdout.log", "w") as log_file:
            p = self._start_reap_popen(launch_args, env=env, cwd=logs_dir, stdout=log_file, stderr=subprocess.STDOUT)
        SubprocessTracker.record(
            self._picoture_pid_dir, p, metadata={"port": self.port}, pid_file_prefix=self.get_pid_prefix()
        )
        return p
