# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: yakpanel
# -------------------------------------------------------------------

# ------------------------------
# Adminer Manager
# ------------------------------

import hashlib
import os
import re
import time
from typing import Tuple

import public
from YakPanel import cache
from panel_plugin_v2 import panelPlugin
from public.exceptions import HintException
from .config import *

www_server = public.get_setup_path()


def get_global_php_versions() -> list:
    from panel_site_v2 import panelSite
    res = panelSite().GetPHPVersion(None, False)
    return [x.get("version", "00") for x in res] if res else []


def find_cgi(file_path: str) -> str:
    try:
        if os.path.exists(file_path):
            content = public.readFile(file_path)
            if content:
                m = re.search(r"php-cgi-(\d+)", content)
                if m:  # ng, ap
                    return f"{m.group(1)[0]}{m.group(1)[1]}"
                ols = re.search(r"lsphp(\d+)", content)
                if ols:  # ols
                    return f"{ols.group(1)[0]}{ols.group(1)[1]}"
    except Exception as e:
        public.print_log('Get Adminer PHP Version Error {}'.format(e))
        pass
    return ""


class AdminerManager:
    INFO_CACHE_KEY = "adminer_port_path"

    @property
    def get_support_versions(self) -> list:
        """支持的版本列表"""
        return list(VER_SHA_MAP.keys())

    @property
    def adminer_dir(self) -> str:
        """目录, 不是绝对路径"""
        dir_path = ""
        if os.path.exists(DEFAULT_DIR):
            for dir_name in os.listdir(DEFAULT_DIR):
                if os.path.isdir(os.path.join(DEFAULT_DIR, dir_name)) and dir_name.startswith("adminer_"):
                    dir_path = dir_name
                    break
        if not dir_path:
            return ""
        return dir_path

    @property
    def adminer_port(self) -> int:
        port = DEFAULT_PORT
        try:
            p = public.readFile(PORT_PL)
            if p and p.strip().isdigit():
                port = int(p.strip())
        except Exception as e:
            public.print_log('Get Adminer PORT PL Error {}'.format(e))

        return int(port)

    @property
    def adminer_dir_port(self) -> Tuple[str, int]:
        c = cache.get(self.INFO_CACHE_KEY)
        if c:
            return c
        path = self.adminer_dir
        port = self.adminer_port
        if not path or not port:
            raise HintException(
                "Adminer is not installed, please go to the [Database] page to install it!"
            )
        info = (path, port)
        cache.set(self.INFO_CACHE_KEY, list(info), 10)
        return info

    @property
    def adminer_version(self) -> str:
        """版本号"""
        try:
            d, p = self.adminer_dir_port
        except HintException:
            return ""
        if not d or not p:
            return ""
        version = DEFAULT_VER
        try:
            if os.path.exists(VERSION_PL):
                with open(VERSION_PL, "r") as f:
                    version = f.read().strip()
        except Exception as e:
            public.print_log('Get Adminer Version Error {}'.format(e))

        return version

    @property
    def adminer_php_version(self) -> str:
        """
        获取adminer使用的PHP版本, 并尝试修复自身
        :return: final_ver = "00" | "74" | "80" | "81" | "82" | "83"...
        "00" 表示没php环境
        """

        def _alive_php(php_vers: list) -> str:
            for v in php_vers:
                if v != "00" and os.path.exists(f"{www_server}/php/{v}/bin/php"):
                    return v
            return "00"

        php_versions = get_global_php_versions()
        if not php_versions:  # 没可用的php版本
            return "00"

        web_server = public.GetWebServer()
        if web_server == "nginx":
            # Nginx 从 enable-php.conf 中找
            enable_cfg = f"{www_server}/nginx/conf/enable-php.conf"

            if os.path.exists(enable_cfg):
                final_ver = find_cgi(enable_cfg)
                if final_ver not in php_versions:
                    # enable-php.conf 里有版本, 但版本不可用时, 进行替换
                    final_ver = _alive_php(php_versions)
                    php_ver_cgi = f"{www_server}/nginx/conf/enable-php-{final_ver}.conf"
                    public.writeFile(enable_cfg, public.readFile(php_ver_cgi))

            else:
                # enable-php.conf 不存在时, 进行覆盖
                final_ver = _alive_php(php_versions)
                php_ver_cgi = f"{www_server}/nginx/conf/enable-php-{final_ver}.conf"
                public.writeFile(enable_cfg, public.readFile(php_ver_cgi))


        else:  # Apache 和 OLS 从各自配置文件中找
            conf = WebConfig.get(web_server)
            if conf and os.path.exists(conf):
                final_ver = find_cgi(conf)
                if final_ver not in php_versions:
                    # 配置里有版本, 但版本不可用时, 仅返回版本号
                    final_ver = _alive_php(php_versions)
            else:
                final_ver = _alive_php(php_versions)

        # 兜底
        if final_ver == "00" or final_ver not in php_versions:
            final_ver = _alive_php(php_versions)
        return final_ver

    @staticmethod
    def require_env(func):
        """环境wrapper"""

        def wrapper(*args, **kwargs):
            # has web server
            if not public.GetWebServer() or not panelPlugin().check_dependent("nginx|apache|openlitespeed"):
                raise HintException("Web service is not installed, please install web service first!")
            # has php version
            for ver in public.get_php_versions():
                if os.path.exists(f"{www_server}/php/{ver}/bin/php"):
                    return func(*args, **kwargs)
            raise HintException("PHP environment is not installed, please install PHP first!")

        return wrapper

    @staticmethod
    def download_official_file(ver: str, dst: str) -> None | str:
        """下载源码"""
        ver_sha256 = VER_SHA_MAP.get(ver, "")
        if not ver_sha256:
            raise Exception(f"Version {ver} not support or wrong, please try again!")

        tmp_dir = "/tmp/adminer_tmp"
        if os.path.exists(tmp_dir):
            public.ExecShell(f"rm -rf {tmp_dir}")
        os.makedirs(tmp_dir, exist_ok=True)
        download_file = f"adminer-{ver}.zip"
        tmp_zip_path = f"{tmp_dir}/{download_file}"
        try:
            download_url = f"{public.get_url()}/src/{download_file}"
            public.downloadFile(download_url, tmp_zip_path)

            if not os.path.exists(tmp_zip_path):
                raise Exception(f"Download {download_file} File not exists, please try again!")

            with open(tmp_zip_path, "rb") as f:
                tmp_sha256 = hashlib.sha256(f.read()).hexdigest()
                public.print_log(f"tmp_sha256= {tmp_sha256}")
            if ver_sha256 != tmp_sha256:
                raise Exception("Downlaod File sha256 check failed, please try again!")

            if not os.path.exists(dst):
                os.makedirs(dst, 0o755, exist_ok=True)

            public.ExecShell(f"unzip -o {tmp_zip_path} -d {dst}")

            file_abs = os.path.join(dst, "index.php")
            if not os.path.exists(file_abs):
                raise Exception("Download official file file failed, please try again!")
            return file_abs
        except Exception as e:
            public.print_log('Download Adminer Error {}'.format(e))
            raise Exception(e)
        finally:
            if os.path.exists(tmp_dir):
                public.ExecShell(f"rm -rf {tmp_dir}")

    @staticmethod
    def generate_conf(web_server: str, force: bool = False, php_ver: str = None) -> None:
        """
        动态生成配置, 覆盖
        依据当前的 port, php_ver, (root_dir强制默认)
        """
        web_conf = WebConfig.get(web_server)
        if not web_conf:
            raise HintException("Web service not support! WEB CONF MAP not found!")

        if public.get_multi_webservice_status():  # 当前是否开启多服务
            web_confs = [web_conf] if web_server == "nginx" else [f"{web_conf}.bak"]
            if web_server != "nginx":  # 非nginx的配置.conf都不应存在
                public.ExecShell(f"rm -f {WebConfig.apache} && rm -f {WebConfig.openlitespeed}")
        else:  # 非多服务
            for w in WebConfig.all_conf:  # .bak不应存在
                if os.path.exists(f"{w}.bak"):
                    public.ExecShell(f"rm -f {w}.bak")
            web_confs = [web_conf]

        # conf | conf.bak
        for conf in web_confs:
            if os.path.exists(conf) and force is False:
                return

            port = DEFAULT_PORT
            port_pl = public.readFile(PORT_PL)
            if port_pl and port_pl.strip().isdigit():
                port = int(port_pl.strip())

            if php_ver is None:
                # nginx 从 enable-php.conf 中找, 其他从自己的配置文件中找
                php_version = AdminerManager().adminer_php_version
            else:
                php_version = php_ver
                # 修改ng的 enable-php.conf
                enable_cfg = f"{www_server}/nginx/conf/enable-php.conf"
                php_ver_cgi = f"{www_server}/nginx/conf/enable-php-{php_version}.conf"
                public.writeFile(enable_cfg, public.readFile(php_ver_cgi))

            if not php_version or php_version == "00":
                raise HintException("PHP environment is not installed, please install PHP first!")

            content = ""
            root_dir = DEFAULT_DIR
            # 模板动态生成配置
            try:
                if web_server == "nginx":
                    content = NG_CONF.format(port=port, root_dir=root_dir)

                elif web_server == "apache":
                    proxy_pass = f"proxy:{public.get_php_proxy(php_version, "apache")}"
                    content = APACHE_CONF.format(port=port, root_dir=root_dir, proxy_pass=proxy_pass)

                elif web_server == "openlitespeed":
                    content = OLS_CONF.format(port=port, root_dir=root_dir, php_version=php_version)

            except Exception as e:
                public.print_log('Generate Adminer Conf Error {}'.format(e))
                pass

            if content:
                public.writeFile(conf, content)

    @staticmethod
    def all_generate_conf(force: bool = False, php_ver: str = None) -> None:
        if php_ver is not None and php_ver not in get_global_php_versions():
            raise HintException(public.lang("PHP version does not install!"))

        for web in WebConfig.all_web:
            AdminerManager.generate_conf(web_server=web, force=force, php_ver=php_ver)

    @property
    def is_install(self) -> bool:
        """是否安装, 只检测入口文件"""
        adminer_dir = self.adminer_dir
        if not adminer_dir:
            return False
        if not os.path.exists(f"{DEFAULT_DIR}/{adminer_dir}/index.php"):
            return False
        mulit = public.get_multi_webservice_status()
        if mulit:
            if os.path.exists(NGX_CONF_PATH):
                return True
        else:
            conf = WebConfig.get(public.GetWebServer())
            if not conf or not os.path.exists(conf):
                return False
        return True

    def service_reload(self):
        public.serviceReload()
        cache.delete(self.INFO_CACHE_KEY)
        time.sleep(0.5)
