import os

from systools import sys_parsefilepath

from application.model import UpdateType
from application.model.customexception import ErrorUpdatingApache
from application.service import UpdaterService
from application.util import (
    LoggerUtil,
    settings,
)

logger = LoggerUtil.get_logger_name(__name__)


class ApacheUpdaterService(object):
    def __init__(
        self,
        updater_service,  # type: UpdaterService
    ):
        # type: (...) -> None

        hv_data_dir = sys_parsefilepath("{HVDATADIR}")

        self.update_type = UpdateType.binaries
        self.update_name = self.update_type.name
        self.updater_service = updater_service
        self.apache_path = os.path.join(
            hv_data_dir,
            "..",
            "..",
            "apache",
        )
        self.apache_conf_path = os.path.join(
            self.apache_path,
            "conf",
        )
        self.apache_conf = os.path.join(
            self.apache_conf_path,
            "httpd.conf",
        )
        self.apache_conf_bkp = os.path.join(
            self.apache_conf_path,
            "httpd.conf.bkp",
        )
        self.apache_conf_bkp = os.path.join(
            self.apache_conf_path,
            "httpd.conf.bkp",
        )
        self.apache_logs_path = os.path.join(
            self.apache_path,
            "logs",
        )
        self.apache_access_log = os.path.join(
            self.apache_logs_path,
            "access.log",
        )
        self.lines_to_uncomment = [
            "#LoadModule deflate_module modules/mod_deflate.so",
            "#LoadModule filter_module modules/mod_filter.so",
            "#LoadModule headers_module modules/mod_headers.so",
        ]
        self.lines_to_comment = [
            'CustomLog "logs/access.log" common',
        ]

        self.deflate_module = "\n<IfModule deflate_module>\n"
        self.deflate_module += "DeflateCompressionLevel 1\n"
        self.deflate_module += "AddOutputFilterByType DEFLATE application/javascript application/rss+xml "
        self.deflate_module += "application/vnd.ms-fontobject application/x-font application/x-font-opentype "
        self.deflate_module += "application/x-font-otf application/x-font-truetype application/x-font-ttf "
        self.deflate_module += "application/x-javascript application/xhtml+xml application/xml font/opentype "
        self.deflate_module += "font/otf font/ttf image/svg+xml image/x-icon text/css text/html text/javascript "
        self.deflate_module += "text/plain text/xml\n"
        self.deflate_module += "</IfModule>\n"

        self.files_config = "<Files index.html>\n"
        self.files_config += "FileETag None\n"
        self.files_config += "Header unset ETag\n"
        self.files_config += """Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"\n"""
        self.files_config += """Header set Pragma "no-cache"\n"""
        self.files_config += "Header set Expires 0\n"
        self.files_config += "</Files>\n"

        self.files_match = """\n<FilesMatch "\\.(jpg|jpeg|png|gif|json|ico|mp3|mp4|ttf)$">\n"""
        self.files_match += """Header set Cache-Control "max-age=0, must-revalidate"\n"""
        self.files_match += "</FilesMatch>\n"

    def update_httpd_conf(self):
        # type: (...) -> None

        settings.set_update_control(
            self.update_name,
            "$UPDATING_APACHE_HTTPD_CONF",
        )
        try:
            logger.info("Updating httpd.conf file.")
            if os.path.exists(self.apache_conf):
                self._create_backup()
                data = self._read_apache_conf()
                configs_to_remove = []
                index_deflate_module = -1
                for i, line in enumerate(data):
                    original_line = line
                    line = line.strip()
                    if line.startswith("#LoadModule"):
                        if line in self.lines_to_uncomment:
                            data[i] = line[1:] + "\n"
                        else:
                            continue
                    elif line.startswith("LoadModule") or line.startswith("#") or line == "":
                        continue
                    elif line in self.lines_to_comment:
                        data[i] = self._get_line_indentation(line=original_line) + "#" + line + "\n"
                    elif line == "LogLevel warn":
                        index_deflate_module = i + 1
                    elif line in ["<IfModule deflate_module>", "<Files index.html>"]:
                        configs_to_remove.append(i)
                    elif line.startswith("<FilesMatch"):
                        configs_to_remove.append(i)

                data = self._remove_configs(
                    data=data,
                    indexes=configs_to_remove,
                )
                data.insert(
                    index_deflate_module,
                    self.deflate_module,
                )
                data.append(self.files_config)
                data.append(self.files_match)
                data = list(
                    filter(
                        None,
                        data,
                    )
                )
                self._write_apache_conf(data=data)
                logger.info("Updated httpd.conf successfully!")
        except Exception as _:
            logger.exception("Error updating httpd.conf file.")
            self._restore_apache_conf()
            raise ErrorUpdatingApache()
        finally:
            self.updater_service.delete_file(path=self.apache_conf_bkp)
            self._clean_access_log_file()

    def _clean_access_log_file(
        self,
    ):
        # type: (...) -> None

        try:
            self.updater_service.delete_file(self.apache_access_log)
        except Exception as _:
            logger.exception("Error cleaning access.log file.")

    def _restore_apache_conf(self):
        try:
            logger.info("Restoring httpd.conf backup.")
            self.updater_service.delete_file(self.apache_conf)
            self.updater_service.copy_file(
                original_file_path=self.apache_conf_bkp,
                destination_file_path=self.apache_conf,
            )
            logger.info("Restored httpd.conf successfully!")
        except Exception as _:
            logger.exception("Error restoring httpd.conf file.")

    def _create_backup(self):
        logger.info("Creating httpd.conf backup.")
        self.updater_service.copy_file(
            original_file_path=self.apache_conf,
            destination_file_path=self.apache_conf_bkp,
        )

    def _read_apache_conf(self):
        # type: (...) -> list

        with open(self.apache_conf, "r") as f:
            data = f.readlines()
        return data

    def _write_apache_conf(
        self,
        data,  # type: list
    ):
        # type: (...) -> None

        with open(self.apache_conf, "w") as f:
            f.writelines(data)

    @staticmethod
    def _remove_configs(
        data,     # type: list
        indexes,  # type: list
    ):
        # type: (...) -> list

        for i in indexes:
            tag = data[i].strip()[1:].split(" ")[0]
            config_end = "</" + tag + ">"
            j = i
            while True:
                line = data[j].strip()
                data[j] = ""
                if line == config_end:
                    if j + 1 < len(data) - 1 and data[j + 1] == "\n":
                        data[j + 1] = ""
                    break
                j += 1

        return data

    @staticmethod
    def _get_line_indentation(
        line,  # type: str
    ):
        # type: (...) -> str

        first_char = line[0]
        valid_chars = [" ", "\t"]
        indentation = ""
        if first_char in valid_chars:
            for c in line:
                if c == first_char:
                    indentation += c
                else:
                    break

        return indentation
