import os
import sys
from pathlib import Path

from bbot.core.helpers.misc import (
    cpu_architecture,
    cpu_architecture_golang,
    cpu_architecture_rust,
    os_platform,
    os_platform_friendly,
)


REQUESTS_PATCHED = False


def increase_limit(new_limit):
    try:
        import resource

        # Get current limit
        soft_limit, hard_limit = resource.getrlimit(resource.RLIMIT_NOFILE)

        new_limit = min(new_limit, hard_limit)

        # Attempt to set new limit
        resource.setrlimit(resource.RLIMIT_NOFILE, (new_limit, hard_limit))
    except Exception as e:
        sys.stderr.write(f"Failed to set new ulimit: {e}\n")


increase_limit(65535)


def add_to_path(v, k="PATH", environ=None):
    """
    Add an entry to a colon-separated PATH variable.
    If it's already contained in the value, shift it to be in first position.
    """
    if environ is None:
        environ = os.environ
    var_list = os.environ.get(k, "").split(":")
    deduped_var_list = []
    for _ in var_list:
        if _ != v and _ not in deduped_var_list:
            deduped_var_list.append(_)
    deduped_var_list = [v] + deduped_var_list
    new_var_str = ":".join(deduped_var_list).strip(":")
    environ[k] = new_var_str


# if we're running in a virtual environment, make sure to include its /bin in PATH
if sys.prefix != sys.base_prefix:
    bin_dir = str(Path(sys.prefix) / "bin")
    add_to_path(bin_dir)

# add ~/.local/bin to PATH
local_bin_dir = str(Path.home() / ".local" / "bin")
add_to_path(local_bin_dir)


class BBOTEnviron:
    def __init__(self, preset):
        self.preset = preset

    def flatten_config(self, config, base="bbot"):
        """
        Flatten a JSON-like config into a sequence of (KEY, value) env var pairs:
            {"modules": {"http": {"threads": 10}}} --> ("BBOT_MODULES_HTTP_THREADS", "10")

        Lists are skipped (they don't translate cleanly to env var values).
        None values are skipped too, since `str(None)` would write the literal
        string "None" into the env, which is never a useful value for a consumer.
        """
        if isinstance(config, dict):
            for k, v in config.items():
                new_base = f"{base}_{k}"
                if isinstance(v, dict):
                    yield from self.flatten_config(v, base=new_base)
                elif v is not None and not isinstance(v, list):
                    yield (new_base.upper(), str(v))

    def prepare(self):
        """
        Sync config to OS environment variables
        """
        environ = dict(os.environ)

        # ensure bbot_tools
        environ["BBOT_TOOLS"] = str(self.preset.core.tools_dir)
        add_to_path(str(self.preset.core.tools_dir), environ=environ)
        # ensure bbot_cache
        environ["BBOT_CACHE"] = str(self.preset.core.cache_dir)
        # ensure bbot_temp
        environ["BBOT_TEMP"] = str(self.preset.core.temp_dir)
        # ensure bbot_lib
        environ["BBOT_LIB"] = str(self.preset.core.lib_dir)
        # export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:~/.bbot/lib/
        add_to_path(str(self.preset.core.lib_dir), k="LD_LIBRARY_PATH", environ=environ)

        # platform variables
        environ["BBOT_OS_PLATFORM"] = os_platform()
        environ["BBOT_OS"] = os_platform_friendly()
        environ["BBOT_CPU_ARCH"] = cpu_architecture()
        environ["BBOT_CPU_ARCH_GOLANG"] = cpu_architecture_golang()
        environ["BBOT_CPU_ARCH_RUST"] = cpu_architecture_rust()

        # copy config to environment
        bbot_environ = self.flatten_config(self.preset.config)
        environ.update(bbot_environ)

        # handle HTTP proxy
        http_proxy = self.preset.config.get("web", {}).get("http_proxy", "")
        if http_proxy:
            environ["HTTP_PROXY"] = http_proxy
            environ["HTTPS_PROXY"] = http_proxy
        else:
            environ.pop("HTTP_PROXY", None)
            environ.pop("HTTPS_PROXY", None)

        # handle proxy exclusions (NO_PROXY)
        http_proxy_exclude = self.preset.config.get("web", {}).get("http_proxy_exclude", [])
        if http_proxy_exclude:
            environ["NO_PROXY"] = ",".join(str(x) for x in http_proxy_exclude)
        else:
            environ.pop("NO_PROXY", None)

        # ssl verification
        import urllib3

        urllib3.disable_warnings()
        ssl_verify_infrastructure = self.preset.config.get("web", {}).get("ssl_verify_infrastructure", True)

        global REQUESTS_PATCHED
        if not ssl_verify_infrastructure and not REQUESTS_PATCHED:
            REQUESTS_PATCHED = True
            import requests
            import functools

            requests.adapters.BaseAdapter.send = functools.partialmethod(
                requests.adapters.BaseAdapter.send, verify=False
            )
            requests.adapters.HTTPAdapter.send = functools.partialmethod(
                requests.adapters.HTTPAdapter.send, verify=False
            )
            requests.Session.request = functools.partialmethod(requests.Session.request, verify=False)
            requests.request = functools.partial(requests.request, verify=False)

        return environ
