import gzip
import logging
import os
from pathlib import Path
import shutil

from defence360agent.utils import atomic_rewrite, check_run, CheckRunError
from im360 import files
from im360.contracts.config import ProactiveDefence

logger = logging.getLogger(__name__)

PHP_IMMUNITY_FILENAME = "/usr/share/i360-php-opts/autorules.yaml"
PHP_IMMUNITY_FILESDIR = Path("/usr/share/i360-php-opts")
PHP_IMMUNITY_COMPILED_RULES = (
    "/usr/share/i360-php-opts/phpimunity.compiled.rules"
)
I360_STORAGE_BIN = "/usr/bin/i360-storage-mkcustom"
I360_STORAGE_NEW_BIN = "/usr/bin/i360-storage-new"
I360_STORAGE_REPLACEHDB_BIN = "/usr/bin/i360-storage-replacehdb-v2"
_RULES_UMASK = 0o022


async def update_php_immunity_files():
    index = files.Index(files.PHP_IMMUNITY, integrity_check=False)
    try:
        item = next(
            i for i in index.items() if i["name"] == "autorules_opt.yaml.gz"
        )
    except StopIteration:
        logger.warning("PHP Immunity rules are not found in index")
        return

    local_path = index.localfilepath(item["url"])
    tmp = PHP_IMMUNITY_FILENAME + ".tmp"
    with gzip.open(local_path, "rt", encoding="utf-8") as src, open(
        tmp, "w"
    ) as dst:
        shutil.copyfileobj(src, dst)
    os.rename(tmp, PHP_IMMUNITY_FILENAME)


async def apply_php_immunity_rules():
    try:
        await check_run([I360_STORAGE_BIN, PHP_IMMUNITY_FILENAME])
    except FileNotFoundError as err:
        logger.error("i360-storage is not found: %s", err)
    except CheckRunError as err:
        logger.error("Failed to apply new php immunity rules: %s", err)


async def update_hook(_, is_updated):
    if is_updated:
        await update_php_immunity_files()
        if (
            ProactiveDefence.PHP_IMMUNITY
            and Path(PHP_IMMUNITY_FILENAME).exists()
        ):
            await apply_php_immunity_rules()


async def update_hook_immunity_v2(_, is_updated):
    """When we are done with downloading
    /var/imunify360/files/php-immunity/v2/* files
    then we need to copy the files as is (no gunzip or byte-compiling)
    to /usr/share/i360-php-opts/
    in a safe way using atomic_rewrite()
    as far these files are opened by i360.so using mmap()
    plus ensure that the files have correct permissions for cagefs
    (/usr/share/i360-php-opts/ directory files
     are visible for cagefs due to imunify360-php-i360 %post scriptlet)
    """

    if is_updated:
        if os.path.exists(I360_STORAGE_REPLACEHDB_BIN):
            await check_run(
                [I360_STORAGE_REPLACEHDB_BIN],
                preexec_fn=lambda: os.umask(_RULES_UMASK),
            )
            return

        index = files.Index(files.PHP_IMMUNITY_V2)
        for wb_db_item in index.items():
            local_path = Path(index.localfilepath(wb_db_item["url"]))

            # As for tough requirements on Proactive defense performance
            # overhead, we do not expect that W/B hash db file
            # to be greater than 10 MiB
            atomic_rewrite(
                PHP_IMMUNITY_FILESDIR / local_path.name,
                local_path.read_bytes(),
                backup=False,
                permissions=0o644,
            )


async def recreate_signatures_on_update(_, is_updated):
    if is_updated:
        # recreate signatures from new rules
        # use *0o022* umask to ensure that rules will be recreated with
        # required permissions
        await check_run(
            [I360_STORAGE_NEW_BIN], preexec_fn=lambda: os.umask(_RULES_UMASK)
        )
