#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import re
import shutil
import sys
from typing import List, Dict

from .config import NGINX_CONF_DIR
from .nginx_utils import NginxUtils
from mod.project.node.dbutil import LoadSite, HttpNode, TcpNode, ServerNodeDB
from typing import Union

from ..nodeutil import ServerNode

if "/www/server/panel/class" not in sys.path:
    sys.path.insert(0, "/www/server/panel/class")

import public


class NginxConfigGenerator:
    def __init__(self):
        self.nginx_conf_dir = NGINX_CONF_DIR
        self.nginx_tcp_conf_dir = os.path.join(self.nginx_conf_dir, "tcp")
        self.nginx_proxy_conf_dir = os.path.join(self.nginx_conf_dir, "proxy")

    def generate_http_config(self, load: LoadSite, nodes: List[HttpNode]) -> Dict[str, str]:
        """生成HTTP负载均衡配置"""
        # 生成upstream配置
        upstream_conf = self._generate_http_upstream_config(load, nodes)

        # 生成server配置
        server_conf = self._generate_server_config(load.site_name)

        # 生成proxy配置
        proxy_conf = self._generate_proxy_config(load, nodes)

        return {
            'upstream': upstream_conf,
            'server': server_conf,
            'proxy': proxy_conf
        }

    @staticmethod
    def _generate_http_upstream_config(load: LoadSite, nodes: List[HttpNode]) -> str:
        """生成upstream配置"""
        config = "upstream {} {{\n".format(load.name)

        # 添加负载均衡方法
        if load.http_config["http_alg"] == "sticky_cookie":
            config += "    sticky name=bt_load_srv_id expires=12h;\n"
        elif load.http_config["http_alg"] == "ip_hash":
            config += "    ip_hash;\n"
        elif load.http_config["http_alg"] == "least_conn":
            config += "    least_conn;\n"

        can_use_backup = not load.http_config["http_alg"] in ["hash", "ip_hash", "random", "sticky_cookie"]

        srv_db = ServerNodeDB()
        # 添加节点
        for node in nodes:
            server = "{}:{}".format(node.node_site_name, node.port)
            if srv_db.is_local_node(node.node_id):
                server = "127.0.0.1:" + str(node.port)
            elif load.site_name == node.node_site_name:
                server_ip = ServerNode.get_node_ip(node.node_id)
                if server_ip:
                    server = server_ip + ":" + str(node.port)
            backup_str = " backup" if node.node_status == "backup" and can_use_backup else ""
            backup_str = " down" if node.node_status == "down" else backup_str
            config += "    server {} weight={} max_fails={} fail_timeout={} {};\n".format(
                server, node.weight, node.max_fail,
                node.fail_timeout, backup_str,
            )
        config += "}\n"
        return config

    @staticmethod
    def _generate_server_config(site_name: str) -> str:
        """生成server配置"""
        site_ng_file = "/www/server/panel/vhost/nginx/{}.conf".format(site_name)
        ng_conf = public.readFile(site_ng_file)
        rep = re.compile(r"include.*/vhost/nginx/proxy/.*\*.conf;")
        if not rep.search(ng_conf):
            ng_proxy_file_str = "/www/server/panel/vhost/nginx/proxy/{}/*.conf;".format(site_name)
            replace_str = "\n\t#引用反向代理规则，注释后配置的反向代理将无效\n\tinclude {}\n\n\tinclude enable-php-".format(
                ng_proxy_file_str)
            ng_conf = ng_conf.replace("include enable-php-", replace_str)
        ng_conf = re.sub(r"location .{3,10}\((gif|js)\|[^{]*[^}]*}\n", "", ng_conf)
        return ng_conf

    @staticmethod
    def _generate_proxy_config(load: LoadSite, nodes: List[HttpNode]) -> str:
        """生成proxy配置"""
        is_https = False
        for node in nodes:
            if node.port in (443, 8443):
                is_https = True
                break

        if not os.path.exists("/www/wwwlogs/load_balancing/logs/" + load.site_name):
            os.makedirs("/www/wwwlogs/load_balancing/logs/" + load.site_name)

        config = """location / {{
    proxy_pass {is_https}://{name};
    
    # 基本代理设置
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    # 禁用缓存
    proxy_cache off;
    # 错误处理
    proxy_next_upstream {next_upstream};
}}

# 使用详细日志格式
access_log /www/wwwlogs/load_balancing/logs/{site_name}/proxy_access.log load_balancer_detailed;
""".format(
            site_name=load.site_name,
            name=load.name,
            is_https="https" if is_https else "http",
            next_upstream=load.http_config["proxy_next_upstream"],
        )
        return config

    @staticmethod
    def generate_tcp_config(load: LoadSite, nodes: List[TcpNode]) -> str:
        """生成TCP配置"""
        tcp_upstream = ""
        for node in nodes:
            backup_str = " backup" if node.node_status == "backup" else ""
            backup_str = " down" if node.node_status == "down" else backup_str
            tcp_upstream += "    server {}:{} weight={} max_fails={} fail_timeout={}{};\n".format(
                node.host, node.port, node.weight, node.max_fail, node.fail_timeout, backup_str
            )
        os.makedirs("/www/wwwlogs/load_balancing/tcp_logs/" + load.name, exist_ok=True)
        tcp_config = load.tcp_config
        config = """upstream %s {
%s
}

server {
    listen %s:%d%s;
    proxy_connect_timeout %ds;
    proxy_timeout %ds;
    proxy_pass %s;
    access_log /www/wwwlogs/load_balancing/tcp_logs/%s/tcp_load.log load_balancer_tcp_detailed;
}
""" % (load.name, tcp_upstream, tcp_config["host"], tcp_config["port"], "" if tcp_config["type"] == "tcp" else " udp",
       tcp_config["proxy_connect_timeout"], tcp_config["proxy_timeout"], load.name, load.name)
        return config

    def save_configs(self, load: LoadSite, nodes: List[Union[TcpNode, HttpNode]]) -> str:
        """保存配置文件"""
        if load.site_type == "http":
            NginxUtils.set_load_balancer_log_format()
            # 保存upstream配置
            configs = self.generate_http_config(load, nodes)
            site_name = load.site_name
            upstream_back, server_back, proxy_back = "", "", ""
            upstream_path = os.path.join(self.nginx_conf_dir, "upstream_{}.conf".format(site_name))
            if os.path.exists(upstream_path):
                upstream_back = public.readFile(upstream_path)
            public.writeFile(upstream_path, configs['upstream'])

            # 保存server配置
            server_path = os.path.join(self.nginx_conf_dir, "{}.conf".format(site_name))
            if os.path.exists(server_path):
                server_back = public.readFile(server_path)
            public.writeFile(server_path, configs['server'])

            # 创建并保存proxy配置
            proxy_dir = os.path.join(self.nginx_proxy_conf_dir, site_name)
            os.makedirs(proxy_dir, exist_ok=True)
            proxy_path = os.path.join(proxy_dir, "load_proxy_{}.conf".format(site_name))
            if os.path.exists(proxy_path):
                proxy_back = public.readFile(proxy_path)
            public.writeFile(proxy_path, configs['proxy'])

            flag, err = NginxUtils.check_config()
            if not flag:
                public.writeFile(upstream_path, upstream_back)
                public.writeFile(server_path, server_back)
                public.writeFile(proxy_path, proxy_back)
                return err
            else:
                NginxUtils.reload_nginx()

        elif load.site_type == "tcp":
            NginxUtils.set_tcp_load_balancer_log_format()
            # 保存TCP配置
            tcp_dir = self.nginx_tcp_conf_dir
            os.makedirs(tcp_dir, exist_ok=True)

            site_name = load.name
            conf = self.generate_tcp_config(load, nodes)
            stream_back = ""
            stream_path = os.path.join(tcp_dir, "{}.conf".format(site_name))
            if os.path.exists(stream_path):
                stream_back = public.readFile(stream_path)
            public.writeFile(stream_path, conf)
            flag, err = NginxUtils.check_config()
            if not flag:
                public.writeFile(stream_path, stream_back)
                return err
            else:
                NginxUtils.reload_nginx()


    def set_http_proxy_next_upstream(self, site_name:  str, proxy_next_upstream: str) -> str:
        proxy_path = os.path.join(self.nginx_proxy_conf_dir, site_name, "load_proxy_{}.conf".format(site_name))
        proxy_back = ""
        if os.path.exists(proxy_path):
            proxy_back = public.readFile(proxy_path)

        rep = re.compile(r'proxy_next_upstream\s+[^;]*;')
        proxy_conf = re.sub(rep, 'proxy_next_upstream {};'.format(proxy_next_upstream), proxy_back)
        if proxy_conf != proxy_back:
            public.writeFile(proxy_path, proxy_conf)
            flag, err = NginxUtils.check_config()
            if not flag:
                public.writeFile(proxy_path, proxy_back)
                return err
            else:
                NginxUtils.reload_nginx()
        return ""


    def delete_node_conf(self, load: LoadSite, mutil = False):
        if load.site_type == "http":
            site_name = load.site_name
            upstream_path = os.path.join(self.nginx_conf_dir, "upstream_{}.conf".format(site_name))
            if os.path.exists(upstream_path):
                os.remove(upstream_path)
            proxy_path = os.path.join(self.nginx_proxy_conf_dir, site_name, "load_proxy_{}.conf".format(site_name))
            if os.path.exists(proxy_path):
                os.remove(proxy_path)

            log_dir = os.path.join("/www/wwwlogs/load_balancing/logs/", site_name)
            if os.path.exists(log_dir):
                shutil.rmtree(log_dir)

            log_cache_dir = os.path.join("/www/wwwlogs/load_balancing/cache/", site_name)
            if os.path.exists(log_cache_dir):
                shutil.rmtree(log_cache_dir)
        else:
            site_name = load.name
            tcp_conf = os.path.join(self.nginx_tcp_conf_dir, "{}.conf".format(site_name))
            if os.path.exists(tcp_conf):
                os.remove(tcp_conf)

            log_dir = os.path.join("/www/wwwlogs/load_balancing/tcp_logs/", site_name)
            if os.path.exists(log_dir):
                shutil.rmtree(log_dir)

            log_cache_dir = os.path.join("/www/wwwlogs/load_balancing/cache/", site_name)
            if os.path.exists(log_cache_dir):
                shutil.rmtree(log_cache_dir)
        if not mutil:
            NginxUtils.reload_nginx()
